pax_global_header00006660000000000000000000000064147755620160014527gustar00rootroot0000000000000052 comment=9a1a74f0aa1c486e04a502c054af8206379a4d94 audmes-2025.04.05/000077500000000000000000000000001477556201600134045ustar00rootroot00000000000000audmes-2025.04.05/.clang-format000066400000000000000000000107231477556201600157620ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -1 AlignAfterOpenBracket: Align AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: true DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Regroup IncludeCategories: - Regex: '^' Priority: 2 SortPriority: 0 - Regex: '^<.*\.h>' Priority: 1 SortPriority: 0 - Regex: '^<.*' Priority: 2 SortPriority: 0 - Regex: '.*' Priority: 3 SortPriority: 0 IncludeIsMainRegex: '([-_](test|unittest))?$' IncludeIsMainSourceRegex: '' IndentCaseLabels: true IndentGotoLabels: true IndentPPDirectives: None IndentWidth: 2 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '^wxBEGIN_EVENT_TABLE' MacroBlockEnd: '^wxEND_EVENT_TABLE' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Never ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left RawStringFormats: - Language: Cpp Delimiters: - cc - CC - cpp - Cpp - CPP - 'c++' - 'C++' CanonicalDelimiter: '' BasedOnStyle: google - Language: TextProto Delimiters: - pb - PB - proto - PROTO EnclosingFunctions: - EqualsProto - EquivToProto - PARSE_PARTIAL_TEXT_PROTO - PARSE_TEST_PROTO - PARSE_TEXT_PROTO - ParseTextOrDie - ParseTextProtoOrDie CanonicalDelimiter: '' BasedOnStyle: google ReflowComments: true SortIncludes: false SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false Standard: Auto StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseCRLF: false UseTab: Never ... audmes-2025.04.05/.clang-tidy000066400000000000000000000010441477556201600154370ustar00rootroot00000000000000--- Checks: > -*, boost-*, bugprone-*, clang-analyzer-*, misc-*, modernize-*, performance-*, portability-*, readability-*, -misc-non-private-member-variables-in-classes, -misc-include-cleaner, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, -modernize-use-using, -performance-no-int-to-ptr, -readability-magic-numbers CheckOptions: - key: readability-implicit-bool-conversion.AllowIntegerConditions value: 1 - key: readability-implicit-bool-conversion.AllowPointerConditions value: 1 ... audmes-2025.04.05/.editorconfig000066400000000000000000000005141477556201600160610ustar00rootroot00000000000000# http://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file, utf-8 charset [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 # c++ and c settings [*.{cpp,c,h}] indent_style = space indent_size = 2 max_line_length = 100 audmes-2025.04.05/.github/000077500000000000000000000000001477556201600147445ustar00rootroot00000000000000audmes-2025.04.05/.github/workflows/000077500000000000000000000000001477556201600170015ustar00rootroot00000000000000audmes-2025.04.05/.github/workflows/main.yml000066400000000000000000000045521477556201600204560ustar00rootroot00000000000000name: Build on: push: branches: [ master, develop ] jobs: Linux: runs-on: ubuntu-latest steps: - name: Install dependencies run: | sudo apt-get update sudo apt-get -y install libwxgtk3.2-dev cmake libfccp-dev libpulse-dev libasound2-dev - name: Checkout repository uses: actions/checkout@v4 - name: Compile and package run: | mkdir build cd build cmake .. cmake --build . --target package - name: Archive build result uses: actions/upload-artifact@v4 with: name: linux-binary path: | build/AudMeS build/AudMeS-*.deb MacOS: runs-on: macos-13 env: CC: cc CXX: c++ CMAKE_BUILD_TYPE: Release MACOSX_DEPLOYMENT_TARGET: 10.14 steps: - name: Install dependencies run: | brew install wxwidgets - name: Checkout uses: actions/checkout@v4 - name: Compile and package run: | git clone https://github.com/ben-strasser/fast-cpp-csv-parser libfccp mkdir build cd build cmake .. cmake --build . --target package - name: Archive build result uses: actions/upload-artifact@v4 with: name: macos-binary path: | build/AudMeS build/AudMeS-*.dmg Windows: runs-on: windows-latest defaults: run: shell: msys2 {0} env: CC: gcc CXX: g++ CMAKE_BUILD_TYPE: Release GENERATOR: 'MinGW Makefiles' steps: - name: Install dependencies uses: msys2/setup-msys2@v2 with: msystem: MINGW32 install: >- base-devel git mingw-w64-i686-gcc mingw-w64-i686-cmake mingw-w64-i686-make mingw-w64-i686-wxwidgets3.2-msw - name: Checkout repository uses: actions/checkout@v4 - name: Compile and package run: | git clone https://github.com/ben-strasser/fast-cpp-csv-parser libfccp mkdir build cd build cmake .. cmake --build . --target package - name: Archive build result uses: actions/upload-artifact@v4 with: name: win-binary path: | build/AudMeS-*.zip audmes-2025.04.05/.gitignore000066400000000000000000000001111477556201600153650ustar00rootroot00000000000000build xcode .DS_Store .vscode .vs libfccp cpp-analyze-cppcheck-build-dir audmes-2025.04.05/.gitlab-ci.yml000066400000000000000000000021751477556201600160450ustar00rootroot00000000000000# This file is a template, and might need editing before it works on your project. # To contribute improvements to CI/CD templates, please follow the Development guide at: # https://docs.gitlab.com/ee/development/cicd/templates.html # This specific template is located at: # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/C++.gitlab-ci.yml # use the official gcc image, based on debian # can use verions as well, like gcc:5.2 # see https://hub.docker.com/_/gcc/ image: gcc:12 build: stage: build # instead of calling g++ directly you can also use some build toolkit like make # install the necessary build tools when needed before_script: - apt update && apt -y install libwxgtk3.2-dev cmake libfccp-dev libpulse-dev libasound2-dev script: - mkdir build && cd build && cmake .. && cmake --build . artifacts: paths: - build/AudMeS # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time # cache: # paths: # - "*.o" # run tests using the binary built before #test: # stage: test # script: # - ./runmytests.sh audmes-2025.04.05/AudMeS.cpp000066400000000000000000001460471477556201600152420ustar00rootroot00000000000000// -*- C++ -*- generated by wxGlade 0.3.1 on Wed Sep 29 16:09:00 2004 /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "AudMeS.h" #include #ifdef __WXMSW__ #include #endif #include #include #include #include #include "dlg_audiointerface.h" #include "event_ids.h" #include "fourier.h" wxIMPLEMENT_CLASS(MainFrame, wxFrame); wxBEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_TOGGLEBUTTON(ID_SPANSTART, MainFrame::OnSpanStart) EVT_TOGGLEBUTTON(ID_GENSTART, MainFrame::OnGenStart) EVT_TOGGLEBUTTON(ID_OSCSTART, MainFrame::OnOscStart) EVT_TOGGLEBUTTON(ID_FRMSTART, MainFrame::OnFrmStart) EVT_MENU(wxID_ABOUT, MainFrame::OnAboutClick) EVT_MENU(wxID_EXIT, MainFrame::OnExitClick) EVT_CLOSE(MainFrame::OnClose) EVT_MENU(ID_SNDCARD, MainFrame::OnSelectSndCard) EVT_TIMER(ID_TIMERID, MainFrame::OnTimer) EVT_CHECKBOX(ID_GENLENB, MainFrame::OnGeneratorChanged) EVT_CHECKBOX(ID_GENRENB, MainFrame::OnGeneratorChanged) EVT_CHECKBOX(ID_GENSYNC, MainFrame::OnGeneratorChanged) EVT_TEXT_ENTER(ID_GENPHASE, MainFrame::OnGeneratorChanged) EVT_COMMAND_SCROLL(ID_GENLFREQ, MainFrame::OnGenScrollLChanged) EVT_COMMAND_SCROLL(ID_GENRFREQ, MainFrame::OnGenScrollRChanged) EVT_COMMAND_SCROLL(ID_GENLAMP, MainFrame::OnGenScrollChanged) EVT_COMMAND_SCROLL(ID_GENRAMP, MainFrame::OnGenScrollChanged) EVT_CHOICE(ID_OSCLTRIG, MainFrame::OnOscChoiceChanged) EVT_CHOICE(ID_OSCRTRIG, MainFrame::OnOscChoiceChanged) EVT_TEXT_ENTER(ID_TXT_FREQ_L, MainFrame::OnTxtFreqLChanged) EVT_TEXT_ENTER(ID_TXT_FREQ_R, MainFrame::OnTxtFreqRChanged) EVT_CHOICE(ID_GENSHP_L, MainFrame::OnGeneratorChanged) EVT_CHOICE(ID_GENSHP_R, MainFrame::OnGeneratorChanged) EVT_MENU(wxID_OPEN, MainFrame::OnOpenClick) EVT_MENU(wxID_SAVE, MainFrame::OnSaveClick) EVT_MENU(wxID_SAVEAS, MainFrame::OnSaveAsClick) EVT_MENU(ID_LOAD_FRM, MainFrame::OnLoadFRM) EVT_MENU(ID_SAVE_FRM, MainFrame::OnSaveFRM) EVT_MENU(ID_SAVE_SPE, MainFrame::OnSaveSPE) EVT_MENU(ID_SAVE_OSC, MainFrame::OnSaveOSC) EVT_BUTTON(ID_AUTOCAL, MainFrame::OnAutoCalClick) EVT_TOGGLEBUTTON(ID_SINC, MainFrame::OnAutoSincClick) EVT_CHOICE(ID_OSCXSCALE, MainFrame::OnOscXScaleChanged) EVT_CHOICE(ID_FFTLENGTH, MainFrame::OnOscXScaleChanged) EVT_CHOICE(ID_FFTAVG, MainFrame::OnFFTAvgChanged) EVT_CHOICE(ID_FFTREF, MainFrame::OnFFTScaleChanged) EVT_CHOICE(ID_FFTDBDIV, MainFrame::OnFFTScaleChanged) wxEND_EVENT_TABLE() float* g_OscBuffer_Left; float* g_OscBuffer_Right; long int g_OscBufferPosition; float* g_SpeBuffer_Left; float* g_SpeBuffer_Right; long int g_SpeBufferPosition; std::atomic g_OscBufferChanged{false}; std::atomic g_SpeBufferChanged{false}; /////////////////////////////////////////////////////////////////////// MainFrame::MainFrame(wxWindow* parent, int id, const wxString& title, const wxPoint& pos, const wxSize& size, long WXUNUSED(style)) : wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) { // begin wxGlade: MainFrame::MainFrame notebook_1 = new wxNotebook(this, -1, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM); notebook_1_spe = new wxPanel(notebook_1, -1); notebook_1_osc = new wxPanel(notebook_1, -1); notebook_1_gen = new wxPanel(notebook_1, -1); notebook_1_frm = new wxPanel(notebook_1, -1); sizer_gen_l_staticbox = new wxStaticBox(notebook_1_gen, -1, wxT("Left channel")); sizer_gen_r_staticbox = new wxStaticBox(notebook_1_gen, -1, wxT("Right channel")); sizer_osc_l_staticbox = new wxStaticBox(notebook_1_osc, -1, wxT("Vertical Left")); sizer_osc_r_staticbox = new wxStaticBox(notebook_1_osc, -1, wxT("Vertical Right")); sizer_osc_h_staticbox = new wxStaticBox(notebook_1_osc, -1, wxT("Horizontal")); sizer_spe_fft_staticbox = new wxStaticBox(notebook_1_spe, -1, wxT("FFT")); sizer_spe_disp_staticbox = new wxStaticBox(notebook_1_spe, -1, wxT("Display")); sizer_spe_scale_staticbox = new wxStaticBox(notebook_1_spe, -1, wxT("Scale")); frame_1_menubar = new wxMenuBar(); wxMenu* wxglade_tmp_menu_1 = new wxMenu(); wxglade_tmp_menu_1->Append(wxID_OPEN, wxT("&Open config...\tAlt+O"), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->Append(wxID_SAVE, wxT("&Save config...\tAlt+S"), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->Append(wxID_SAVEAS, wxT("Save &As"), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->Append(ID_LOAD_FRM, wxT("Load freq.resp."), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->Append(ID_SAVE_FRM, wxT("Save freq.resp."), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->Append(ID_SAVE_SPE, wxT("Save spectrum"), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->Append(ID_SAVE_OSC, wxT("Save oscillogram"), wxT(""), wxITEM_NORMAL); wxglade_tmp_menu_1->AppendSeparator(); wxglade_tmp_menu_1->Append(wxID_EXIT, wxT("&Close\tAlt+F4"), wxT(""), wxITEM_NORMAL); frame_1_menubar->Append(wxglade_tmp_menu_1, wxT("&File")); wxMenu* wxglade_tmp_menu_2 = new wxMenu(); wxglade_tmp_menu_2->Append(ID_SNDCARD, wxT("Audio &Interface Configuration..."), wxT(""), wxITEM_NORMAL); frame_1_menubar->Append(wxglade_tmp_menu_2, wxT("&Tools")); wxMenu* wxglade_tmp_menu_3 = new wxMenu(); wxglade_tmp_menu_3->Append(wxID_ABOUT, wxT("&About..."), wxT(""), wxITEM_NORMAL); frame_1_menubar->Append(wxglade_tmp_menu_3, wxT("&Help")); SetMenuBar(frame_1_menubar); frame_1_statusbar = CreateStatusBar(1, 0); /* generator panel */ checkbox_l_en = new wxCheckBox(notebook_1_gen, ID_GENLENB, wxT("Enable Output")); label_gen_wave_l = new wxStaticText(notebook_1_gen, -1, wxT("Waveform: ")); const wxString choice_l_wav_choices[] = {wxT("Sine"), wxT("Rectangular"), wxT("Saw"), wxT("Triangle"), wxT("Wh-Noise"), wxT("Wobble")}; choice_l_wav = new wxChoice(notebook_1_gen, ID_GENSHP_L, wxDefaultPosition, wxDefaultSize, 6, choice_l_wav_choices, 0); label__gen_freq_l = new wxStaticText(notebook_1_gen, -1, wxT("Frequency [20..20000Hz]: ")); slide_l_fr = new wxSlider(notebook_1_gen, ID_GENLFREQ, 80, 0, 200); label_gen_ampl_l = new wxStaticText(notebook_1_gen, -1, wxT("Amplitude [0..-60dB]: ")); slide_l_am = new wxSlider(notebook_1_gen, ID_GENLAMP, 0, -60, 0); checkbox_r_en = new wxCheckBox(notebook_1_gen, ID_GENRENB, wxT("Enable Output")); label_gen_wave_r = new wxStaticText(notebook_1_gen, -1, wxT("Waveform: ")); const wxString choice_r_wav_choices[] = {wxT("Sine"), wxT("Rectangular"), wxT("Saw"), wxT("Triangle"), wxT("Wh-Noise"), wxT("Wobble")}; choice_r_wav = new wxChoice(notebook_1_gen, ID_GENSHP_R, wxDefaultPosition, wxDefaultSize, 6, choice_r_wav_choices, 0); label_gen_freq_r = new wxStaticText(notebook_1_gen, -1, wxT("Frequency [20..20000Hz]: ")); slide_r_fr = new wxSlider(notebook_1_gen, ID_GENRFREQ, 80, 0, 200); label_gen_ampl_r = new wxStaticText(notebook_1_gen, -1, wxT("Amplitude [0..-60dB]: ")); slide_r_am = new wxSlider(notebook_1_gen, ID_GENRAMP, 0, -60, 0); button_gen_start = new wxToggleButton(notebook_1_gen, ID_GENSTART, wxT("Start")); checkbox_gen_sync = new wxCheckBox(notebook_1_gen, ID_GENSYNC, wxT("L and R are synchronized")); label_gen_sync = new wxStaticText(notebook_1_gen, -1, wxT("Phase between L and R [0..360 degrees]: ")); text_gen_sync = new wxTextCtrl(notebook_1_gen, ID_GENPHASE, wxT("0"), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); txt_freq_l = new wxTextCtrl(notebook_1_gen, ID_TXT_FREQ_L, wxT("315.0"), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); txt_freq_r = new wxTextCtrl(notebook_1_gen, ID_TXT_FREQ_R, wxT("315.0"), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); /* oscilloscope panel */ window_osc = new CtrlOScope(notebook_1_osc, _T(""), _T("")); label_osc_div_l = new wxStaticText(notebook_1_osc, -1, wxT("[V/div]: ")); const wxString choice_osc_l_res_choices[] = { wxT("0.2"), wxT("0.1"), wxT("50m"), wxT("20m"), wxT("10m"), wxT("5m"), wxT("2m"), wxT("1m"), wxT("500u"), wxT("200u"), wxT("100u"), wxT("50u"), wxT("20u"), wxT("10u"), }; choice_osc_l_res = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 14, choice_osc_l_res_choices, 0); label_osc_off_l = new wxStaticText(notebook_1_osc, -1, wxT("Offset: ")); const wxString choice_osc_l_off_choices[] = {wxT("1"), wxT("0.8"), wxT("0.6"), wxT("0.4"), wxT("0.2"), wxT("0"), wxT("-0.2"), wxT("-0.4"), wxT("-0.6"), wxT("-0.8"), wxT("-1")}; choice_osc_l_off = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 11, choice_osc_l_off_choices, 0); label_osc_div_r = new wxStaticText(notebook_1_osc, -1, wxT("[V/div]: ")); const wxString choice_osc_r_res_choices[] = { wxT("0.2"), wxT("0.1"), wxT("50m"), wxT("20m"), wxT("10m"), wxT("5m"), wxT("2m"), wxT("1m"), wxT("500u"), wxT("200u"), wxT("100u"), wxT("50u"), wxT("20u"), wxT("10u"), }; choice_osc_r_res = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 14, choice_osc_r_res_choices, 0); label_osc_off_r = new wxStaticText(notebook_1_osc, -1, wxT("Offset: ")); const wxString choice_osc_r_off_choices[] = {wxT("1"), wxT("0.8"), wxT("0.6"), wxT("0.4"), wxT("0.2"), wxT("0"), wxT("-0.2"), wxT("-0.4"), wxT("-0.6"), wxT("-0.8"), wxT("-1")}; choice_osc_r_off = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 11, choice_osc_r_off_choices, 0); button_autocalibrate = new wxButton(notebook_1_osc, ID_AUTOCAL, wxT("V Autorange")); button_sinc = new wxToggleButton(notebook_1_osc, ID_SINC, wxT("sin(x)/x")); const wxString choice_osc_swp_choices[] = { wxT("10"), wxT("20"), wxT("50"), wxT("100"), wxT("200"), wxT("500"), wxT("1000"), wxT("2000"), wxT("5000"), wxT("10000"), wxT("20000"), wxT("50000"), }; choice_osc_swp = new wxChoice(notebook_1_osc, ID_OSCXSCALE, wxDefaultPosition, wxDefaultSize, 12, choice_osc_swp_choices, 0); label_osc_time = new wxStaticText(notebook_1_osc, -1, wxT("Time [us/div]: ")); // triggering control label_8 = new wxStaticText(notebook_1_osc, -1, wxT("Trigger: ")); const wxString choice_osc_trig_source_choices[] = {wxT("Off"), wxT("Left channel"), wxT("Right channel")}; choice_osc_trig_source = new wxChoice(notebook_1_osc, ID_OSCLTRIG, wxDefaultPosition, wxDefaultSize, 3, choice_osc_trig_source_choices, 0); label_osc_trig = new wxStaticText(notebook_1_osc, -1, wxT("Trigger edge: ")); const wxString choice_osc_trig_edge_choices[] = {wxT("Rising edge"), wxT("Falling edge")}; choice_osc_trig_edge = new wxChoice(notebook_1_osc, ID_OSCRTRIG, wxDefaultPosition, wxDefaultSize, 2, choice_osc_trig_edge_choices, 0); button_osc_start = new wxToggleButton(notebook_1_osc, ID_OSCSTART, wxT("Start")); /* Spectrum analyzer */ label_5 = new wxStaticText(notebook_1_spe, -1, wxT("FFT Window Type:")); const wxString choice_fft_choices[] = {wxT("Rect"), wxT("Hanning"), wxT("Blackman"), wxT("BlackHarr")}; choice_fft = new wxChoice(notebook_1_spe, ID_FFTWINDOW, wxDefaultPosition, wxDefaultSize, 4, choice_fft_choices, 0); label_9 = new wxStaticText(notebook_1_spe, -1, wxT("Number of samples:")); const wxString choice_fftlength_choices[] = {wxT("128"), wxT("256"), wxT("512"), wxT("1024"), wxT("2048"), wxT("4096"), wxT("8192"), wxT("16384"), wxT("32768"), wxT("65536")}; choice_fftlength = new wxChoice(notebook_1_spe, ID_FFTLENGTH, wxDefaultPosition, wxDefaultSize, 10, choice_fftlength_choices, 0); label_rx = new wxStaticText(notebook_1_spe, -1, wxT("Freq:")); const wxString choice_fftry_choices[] = {wxT("2-2000"), wxT("20-20k"), wxT("10-100k")}; choice_fftrx = new wxChoice(notebook_1_spe, ID_FFTWINDOW, wxDefaultPosition, wxDefaultSize, 3, choice_fftry_choices, 0); label_avg = new wxStaticText(notebook_1_spe, -1, wxT("Averaging (N):")); const wxString choice_fftavg_choices[] = {wxT("1"), wxT("2"), wxT("5"), wxT("10"), wxT("20")}; choice_fftavg = new wxChoice(notebook_1_spe, ID_FFTAVG, wxDefaultPosition, wxDefaultSize, 5, choice_fftavg_choices, 0); label_spe_ref = new wxStaticText(notebook_1_spe, -1, wxT("Ref Level [dB]")); const wxString choice_spe_ref_choices[] = {wxT("0"), wxT("-10"), wxT("-20"), wxT("-30"), wxT("-40"), wxT("-50")}; choice_spe_ref = new wxChoice(notebook_1_spe, ID_FFTREF, wxDefaultPosition, wxDefaultSize, 6, choice_spe_ref_choices, 0); label_spe_dbdiv = new wxStaticText(notebook_1_spe, -1, wxT("Amplitude [dB/div]")); const wxString choice_spe_dbdiv_choices[] = {wxT("3"), wxT("5"), wxT("10")}; choice_spe_dbdiv = new wxChoice(notebook_1_spe, ID_FFTDBDIV, wxDefaultPosition, wxDefaultSize, 3, choice_spe_dbdiv_choices, 0); window_1_spe = new CtrlOScope(notebook_1_spe, _T("Hz"), _T("dB")); button_spe_start = new wxToggleButton(notebook_1_spe, ID_SPANSTART, wxT("Start")); /* Frequency response */ label_1_frm = new wxStaticText(notebook_1_frm, -1, wxT("Number of points (max 120):")); text_ctrl1_frm = new wxTextCtrl(notebook_1_frm, -1, wxT("24")); label_2_frm = new wxStaticText(notebook_1_frm, -1, wxT("-")); text_ctrl2_frm = new wxTextCtrl(notebook_1_frm, -1, wxT("-")); button_frm_start = new wxToggleButton(notebook_1_frm, ID_FRMSTART, wxT("Start")); window_1_frm = new CtrlOScope(notebook_1_frm, _T("Hz"), _T("dB")); set_properties(); do_layout(); // end wxGlade set_custom_props(); } void MainFrame::set_properties() { // begin wxGlade: MainFrame::set_properties SetTitle(wxT("AUDio MEasurement System")); int frame_1_statusbar_widths[] = {-1}; frame_1_statusbar->SetStatusWidths(1, frame_1_statusbar_widths); frame_1_statusbar->SetStatusText("AUDio MEasurement System - version " AUDMES_VERSION_STRING); choice_l_wav->SetSelection(0); choice_r_wav->SetSelection(0); choice_osc_swp->SetSelection(6); choice_osc_l_res->SetSelection(0); choice_osc_l_off->SetSelection(0); choice_osc_trig_source->SetSelection(0); choice_osc_r_res->SetSelection(0); choice_osc_r_off->SetSelection(0); choice_osc_trig_edge->SetSelection(0); choice_fft->SetSelection(1); choice_fftlength->SetSelection(4); choice_fftrx->SetSelection(2); choice_fftavg->SetSelection(0); choice_spe_ref->SetSelection(0); choice_spe_dbdiv->SetSelection(2); // end wxGlade } void MainFrame::do_layout() { // begin wxGlade: MainFrame::do_layout wxBoxSizer* sizer_notebook = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_2 = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_3 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_spe_9 = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_spe_10 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_9 = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_10 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_11 = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_11B = new wxBoxSizer(wxHORIZONTAL); wxStaticBoxSizer* sizer_12L = new wxStaticBoxSizer(sizer_osc_l_staticbox, wxVERTICAL); wxBoxSizer* sizer_16L = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_15L = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_14L = new wxBoxSizer(wxHORIZONTAL); wxStaticBoxSizer* sizer_12R = new wxStaticBoxSizer(sizer_osc_r_staticbox, wxVERTICAL); wxStaticBoxSizer* sizer_12H = new wxStaticBoxSizer(sizer_osc_h_staticbox, wxVERTICAL); wxBoxSizer* sizer_16R = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_15R = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_14R = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_13 = new wxBoxSizer(wxHORIZONTAL); wxStaticBoxSizer* sizer_GenEnL = new wxStaticBoxSizer(sizer_gen_l_staticbox, wxVERTICAL); wxStaticBoxSizer* sizer_GenEnR = new wxStaticBoxSizer(sizer_gen_r_staticbox, wxVERTICAL); wxBoxSizer* sizer_gen_sync = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_gen_sync2 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_9_frm = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_10_frm = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_17_frm = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_txtfreql = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_txtfreqr = new wxBoxSizer(wxVERTICAL); wxFlexGridSizer* sizer_GenL = new wxFlexGridSizer(3, 2, 5, 5); wxFlexGridSizer* sizer_GenR = new wxFlexGridSizer(3, 2, 5, 5); wxBoxSizer* sizer_spe_ctrl = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_spe_window = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_spe_samples = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_spe_span = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_spe_avg = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_spe_ref = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_spe_dbdiv = new wxBoxSizer(wxHORIZONTAL); wxStaticBoxSizer* sizer_spe_fft = new wxStaticBoxSizer(sizer_spe_fft_staticbox, wxVERTICAL); wxStaticBoxSizer* sizer_spe_disp = new wxStaticBoxSizer(sizer_spe_disp_staticbox, wxVERTICAL); wxStaticBoxSizer* sizer_spe_scale = new wxStaticBoxSizer(sizer_spe_scale_staticbox, wxVERTICAL); // generator sizer_GenEnL->Add(checkbox_l_en, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_GenL->Add(label_gen_wave_l, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_GenL->Add(choice_l_wav, 1, wxALL | wxEXPAND, 5); sizer_GenL->Add(label__gen_freq_l, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_txtfreql->Add(slide_l_fr, 0, wxEXPAND, 5); sizer_txtfreql->Add(txt_freq_l, wxALL | wxEXPAND, 5); sizer_GenL->Add(sizer_txtfreql, 1, wxALL | wxEXPAND, 5); sizer_GenL->Add(label_gen_ampl_l, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_GenL->Add(slide_l_am, 1, wxEXPAND, 5); sizer_GenEnL->Add(sizer_GenL, 0, wxALL | wxEXPAND, 5); sizer_3->Add(sizer_GenEnL, 0, wxALL | wxEXPAND, 5); sizer_GenEnR->Add(checkbox_r_en, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_GenR->Add(label_gen_wave_r, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_GenR->Add(choice_r_wav, 1, wxALL | wxEXPAND, 5); sizer_GenR->Add(label_gen_freq_r, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_txtfreqr->Add(slide_r_fr, 0, wxEXPAND, 5); sizer_txtfreqr->Add(txt_freq_r, wxALL | wxEXPAND, 5); sizer_GenR->Add(sizer_txtfreqr, 1, wxALL | wxEXPAND, 5); sizer_GenR->Add(label_gen_ampl_r, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_GenR->Add(slide_r_am, 1, wxEXPAND, 5); sizer_GenEnR->Add(sizer_GenR, 0, wxALL | wxEXPAND, 5); sizer_3->Add(sizer_GenEnR, 0, wxALL | wxEXPAND, 5); sizer_2->Add(sizer_3, 0, wxALIGN_CENTER_HORIZONTAL, 0); sizer_gen_sync->Add(checkbox_gen_sync, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_gen_sync2->Add(label_gen_sync, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_gen_sync2->Add(text_gen_sync, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_2->Add(sizer_gen_sync, 0, wxALIGN_CENTER_HORIZONTAL, 0); sizer_2->Add(sizer_gen_sync2, 0, wxALIGN_CENTER_HORIZONTAL, 0); sizer_2->Add(button_gen_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); notebook_1_gen->SetAutoLayout(true); notebook_1_gen->SetSizer(sizer_2); sizer_2->Fit(notebook_1_gen); sizer_2->SetSizeHints(notebook_1_gen); // oscilloscope sizer_10->Add(window_osc, 1, wxEXPAND, 0); // CtrlOScope // sizer_14: wxHorizontal sizer_14L->Add(label_osc_div_l, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); // Res[V/Div] sizer_14L->Add(5, 5, 1, 0, 0); // spacer sizer_14L->Add(choice_osc_l_res, 0, wxALL, 5); // 2, 4, 8......32768 // sizer_12: StaticBox Left Channel (Red) sizer_12L->Add(sizer_14L, 1, wxEXPAND, 0); sizer_15L->Add(label_osc_off_l, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); // Offset [V/div] sizer_15L->Add(5, 5, 1, 0, 0); // spacer sizer_15L->Add(choice_osc_l_off, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 5); // 100, 80, ... -100 sizer_12L->Add(sizer_15L, 1, wxEXPAND, 0); sizer_11B->Add(button_autocalibrate, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 5); sizer_11B->Add(5, 5, 1, 0, 0); // spacer sizer_11B->Add(button_sinc, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5); sizer_11->Add(sizer_11B, 0, wxALL | wxEXPAND, 5); sizer_11->Add(sizer_12L, 0, wxALL | wxEXPAND, 5); sizer_14R->Add(label_osc_div_r, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); // Res[V/div] sizer_14R->Add(5, 5, 1, 0, 0); // spacer sizer_14R->Add(choice_osc_r_res, 0, wxALL, 5); // 2, 4, ... 32768 // sizer_12_copy: StaticBox Right Channel (Green) sizer_12R->Add(sizer_14R, 1, wxEXPAND, 0); sizer_15R->Add(label_osc_off_r, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); // Offset [V/div] sizer_15R->Add(5, 5, 1, 0, 0); // spacer sizer_15R->Add(choice_osc_r_off, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 5); // 100, 80, ... -100 sizer_12R->Add(sizer_15R, 1, wxEXPAND, 0); sizer_11->Add(sizer_12R, 0, wxALL | wxEXPAND, 5); // sizer_13: wxHORIZONTAL sizer_13->Add(label_osc_time, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); // X Scale time sizer_13->Add(5, 5, 1, 0, 0); // spacer sizer_13->Add(choice_osc_swp, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); // 20, 50....50000 sizer_12H->Add(sizer_13, 1, wxEXPAND, 0); sizer_16L->Add(label_8, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); // Trigger sizer_16L->Add(5, 5, 1, 0, 0); // spacer sizer_16L->Add(choice_osc_trig_source, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); // Off, Left, ... Channel sizer_12H->Add(sizer_16L, 1, wxEXPAND, 0); sizer_16R->Add(label_osc_trig, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); // Trigger Edge sizer_16R->Add(5, 5, 1, 0, 0); // spacer sizer_16R->Add(choice_osc_trig_edge, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); // Rising, ... edge sizer_12H->Add(sizer_16R, 1, wxEXPAND, 0); sizer_11->Add(sizer_12H, 1, wxEXPAND, 0); sizer_10->Add(sizer_11, 0, 0, 0); sizer_9->Add(sizer_10, 1, wxEXPAND, 0); sizer_9->Add(button_osc_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); notebook_1_osc->SetAutoLayout(true); notebook_1_osc->SetSizer(sizer_9); sizer_9->Fit(notebook_1_osc); sizer_9->SetSizeHints(notebook_1_osc); // analyzer sizer_spe_10->Add(window_1_spe, 1, wxEXPAND, 0); wxSize window_1_spe_size = window_1_spe->GetSize(); sizer_spe_10->SetMinSize(4 * window_1_spe_size.GetHeight(), window_1_spe_size.GetHeight()); sizer_spe_window->Add(label_5, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_spe_window->Add(5, 5, 1, 0, 0); sizer_spe_window->Add(choice_fft, 0, wxALL, 5); sizer_spe_fft->Add(sizer_spe_window, 1, wxEXPAND, 0); sizer_spe_samples->Add(label_9, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_spe_samples->Add(5, 5, 1, 0, 0); sizer_spe_samples->Add(choice_fftlength, 0, wxALL, 5); sizer_spe_fft->Add(sizer_spe_samples, 1, wxEXPAND, 0); sizer_spe_ctrl->Add(sizer_spe_fft, 0, wxALL | wxEXPAND, 5); sizer_spe_span->Add(label_rx, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_spe_span->Add(5, 5, 1, 0, 0); sizer_spe_span->Add(choice_fftrx, 0, wxALL, 5); sizer_spe_disp->Add(sizer_spe_span, 1, wxEXPAND, 0); sizer_spe_avg->Add(label_avg, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_spe_avg->Add(5, 5, 1, 0, 0); sizer_spe_avg->Add(choice_fftavg, 0, wxALL, 5); sizer_spe_disp->Add(sizer_spe_avg, 1, wxEXPAND, 0); sizer_spe_ctrl->Add(sizer_spe_disp, 0, wxALL | wxEXPAND, 5); sizer_spe_ref->Add(label_spe_ref, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_spe_ref->Add(5, 5, 1, 0, 0); sizer_spe_ref->Add(choice_spe_ref, 0, wxALL, 5); sizer_spe_scale->Add(sizer_spe_ref, 1, wxEXPAND, 0); sizer_spe_dbdiv->Add(label_spe_dbdiv, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_spe_dbdiv->Add(5, 5, 1, 0, 0); sizer_spe_dbdiv->Add(choice_spe_dbdiv, 0, wxALL, 5); sizer_spe_scale->Add(sizer_spe_dbdiv, 1, wxEXPAND, 0); sizer_spe_ctrl->Add(sizer_spe_scale, 0, wxALL | wxEXPAND, 5); sizer_spe_10->Add(sizer_spe_ctrl, 0, wxEXPAND, 0); sizer_spe_9->Add(sizer_spe_10, 1, wxEXPAND, 0); sizer_spe_9->Add(button_spe_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); notebook_1_spe->SetAutoLayout(true); notebook_1_spe->SetSizer(sizer_spe_9); sizer_spe_9->Fit(notebook_1_spe); sizer_spe_9->SetSizeHints(notebook_1_spe); // frequency response sizer_17_frm->Add(label_1_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_17_frm->Add(text_ctrl1_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_17_frm->Add(20, 20, 0, 0, 0); sizer_17_frm->Add(label_2_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_17_frm->Add(text_ctrl2_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); sizer_17_frm->Add(20, 20, 0, 0, 0); sizer_10_frm->Add(sizer_17_frm, 0, wxEXPAND, 0); sizer_10_frm->Add(window_1_frm, 1, wxEXPAND, 0); sizer_9_frm->Add(sizer_10_frm, 1, wxEXPAND, 0); sizer_9_frm->Add(button_frm_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); notebook_1_frm->SetAutoLayout(true); notebook_1_frm->SetSizer(sizer_9_frm); sizer_9_frm->Fit(notebook_1_frm); sizer_9_frm->SetSizeHints(notebook_1_frm); // main notebook notebook_1->AddPage(notebook_1_gen, wxT("Generator")); notebook_1->AddPage(notebook_1_osc, wxT("Oscilloscope")); notebook_1->AddPage(notebook_1_spe, wxT("Spectrum Analyzer")); notebook_1->AddPage(notebook_1_frm, wxT("Frequency Response")); sizer_notebook->Add(notebook_1, 1, wxEXPAND, 0); SetAutoLayout(true); SetSizer(sizer_notebook); sizer_notebook->Fit(this); sizer_notebook->SetSizeHints(this); Layout(); // end wxGlade } void MainFrame::setoscbuf() { m_OscBufferLength = (unsigned long)(2 + sweep_div * 10E-6 * m_SamplingFreq); } void MainFrame::set_custom_props() { #ifdef __WXMSW__ SetIcon(wxICON(AudMeSIcon)); #endif #ifdef __LINUX__ #include "audmes.xpm" SetIcon(wxICON(audmes)); #endif m_PlayDev = 0; m_RecordDev = 0; m_SamplingFreq = 44100; choice_osc_l_res->SetSelection(0); choice_osc_r_res->SetSelection(0); choice_osc_l_off->SetSelection(5); choice_osc_r_off->SetSelection(5); sweep_div = wxAtoi(choice_osc_swp->GetString(choice_osc_swp->GetSelection())); setoscbuf(); /* oscilloscope */ window_osc->SetXRange(0, sweep_div * 10E-6, 0); window_osc->SetYRange(-1, 1, 0); window_osc->SetNumOfVerticals(10); /* analyzer */ window_1_spe->SetXRange(10, 100000, 1); window_1_spe->SetYRange(-100, 0, 0); /* freq response */ window_1_frm->SetXRange(20, 20000, 1); window_1_frm->SetYRange(-80, 0, 0); frm_running = 0; frm_measure = 0; frm_istep = 0; m_configfilename = wxT(""); m_timer = new wxTimer(this, ID_TIMERID); m_timer->Start(100); m_SpeBufferLength = wxAtoi(choice_fftlength->GetString(choice_fftlength->GetSelection())); m_RWAudio = new RWAudio(); m_SMASpeLeft = new SMA_2D(m_SpeBufferLength >> 1, 1); m_SMASpeRight = new SMA_2D(m_SpeBufferLength >> 1, 1); int ret = 0; ret = m_RWAudio->InitSnd((long int)(2.0 * m_OscBufferLength), m_SpeBufferLength, m_rtinfo, m_SamplingFreq); if (ret) wxMessageBox(_T("Sound card issue:\n\nPlease check\nTools -> Audio interface Configuration\n"), _T("Alert"), wxICON_INFORMATION | wxOK); } void MainFrame::OnAboutClick(wxCommandEvent& WXUNUSED(event)) { wxString s; s << wxT("AUDio MEasurement System - version ") << AUDMES_VERSION_STRING << wxT("\nVaclav Peroutka - vaclavpe@seznam.cz\n\n") << wxT("Project page: https://sourceforge.net/projects/audmes/\n\n") << m_rtinfo; wxMessageBox(s, _T("About application"), wxICON_INFORMATION | wxOK); } void MainFrame::OnExitClick(wxCommandEvent& WXUNUSED(event)) { Close(); } void MainFrame::OnClose(wxCloseEvent& WXUNUSED(event)) { m_timer->Stop(); Destroy(); } void MainFrame::OnOpenClick(wxCommandEvent& WXUNUSED(event)) { wxMessageBox(wxT("Not yet implemented"), _T("About application"), wxICON_INFORMATION | wxOK); } void MainFrame::OnSaveClick(wxCommandEvent& WXUNUSED(event)) { wxMessageBox(wxT("Not yet implemented"), _T("About application"), wxICON_INFORMATION | wxOK); } void MainFrame::OnSaveAsClick(wxCommandEvent& WXUNUSED(event)) { wxMessageBox(wxT("Not yet implemented"), _T("About application"), wxICON_INFORMATION | wxOK); } void MainFrame::OnSaveFRM(wxCommandEvent& WXUNUSED(event)) { wxFileDialog saveFileDialog(this, _("Save frequency response file"), "", "", "CSV files (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) return; std::ofstream frm; frm.open(saveFileDialog.GetPath().mb_str(), std::ios::trunc); if (!frm.is_open()) { wxLogError("Cannot save current contents in file '%s'.", saveFileDialog.GetPath()); return; } frm << "Hz" << "," << "GainL" << "," << "GainR" << std::endl; for (unsigned int i = 0; i < frm_freqs.GetCount(); i++) { frm << frm_freqs[i] << "," << frm_lgains[i] << "," << frm_rgains[i] << std::endl; } frm.close(); } void MainFrame::OnSaveSPE(wxCommandEvent& WXUNUSED(event)) { wxFileDialog saveFileDialog(this, _("Save frequency spectrum file"), "", "", "CSV files (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) return; std::ofstream spe; spe.open(saveFileDialog.GetPath().mb_str(), std::ios::trunc); if (!spe.is_open()) { wxLogError("Cannot save current contents in file '%s'.", saveFileDialog.GetPath()); return; } spe << "Hz" << "," << "MagL" << "," << "MagR" << std::endl; for (unsigned int i = 0; i < spe_freqs.GetCount(); i++) { spe << spe_freqs[i] << "," << spe_lmagns[i] << "," << spe_rmagns[i] << std::endl; } spe.close(); } void MainFrame::OnSaveOSC(wxCommandEvent& WXUNUSED(event)) { wxFileDialog saveFileDialog(this, _("Save oscillogram file"), "", "", "CSV files (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) return; std::ofstream osc; osc.open(saveFileDialog.GetPath().mb_str(), std::ios::trunc); if (!osc.is_open()) { wxLogError("Cannot save current contents in file '%s'.", saveFileDialog.GetPath()); return; } osc << "time" << "," << "MagL" << "," << "MagR" << std::endl; for (unsigned int i = 0; i < osc_times.GetCount(); i++) { osc << osc_times[i] << "," << osc_lmagns[i] << "," << osc_rmagns[i] << std::endl; } osc.close(); } void MainFrame::OnLoadFRM(wxCommandEvent& WXUNUSED(event)) { if (0 /* ...current content has not been saved... */) { if (wxMessageBox(_("Current content has not been saved! Proceed?"), _("Please confirm"), wxICON_QUESTION | wxYES_NO, this) == wxNO) return; } wxFileDialog openFileDialog(this, _("Open frequency response file"), "", "", "CSV files (*.csv)|*.csv", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() == wxID_CANCEL) return; io::CSVReader<3> in(openFileDialog.GetPath()); in.read_header(io::ignore_extra_column, "Hz", "GainL", "GainR"); frm_freqs.Clear(); frm_lgains.Clear(); frm_rgains.Clear(); double hz; double gainl, gainr; while (in.read_row(hz, gainl, gainr)) { frm_freqs.Add(hz); frm_lgains.Add(gainl); frm_rgains.Add(gainr); } DrawFreqResponse(); Refresh(); } void MainFrame::OnAutoCalClick(wxCommandEvent& WXUNUSED(event)) { if (button_osc_start->GetValue()) { float minValueL = 1; float maxValueL = -1; float minValueR = 1; float maxValueR = -1; for (unsigned long int i = 0; i < m_OscBufferLength; i++) { if (minValueL > g_OscBuffer_Left[i]) minValueL = g_OscBuffer_Left[i]; if (maxValueL < g_OscBuffer_Left[i]) maxValueL = g_OscBuffer_Left[i]; if (minValueR > g_OscBuffer_Right[i]) minValueR = g_OscBuffer_Right[i]; if (maxValueR < g_OscBuffer_Right[i]) maxValueR = g_OscBuffer_Right[i]; } auto diff = 1 / (maxValueL - minValueL); auto lgdiff = log(diff) / log(2); if (lgdiff > 13) lgdiff = 13; choice_osc_l_res->SetSelection((int)lgdiff); diff = 1 / (maxValueR - minValueR); lgdiff = log(diff) / log(2); if (lgdiff > 13) lgdiff = 13; choice_osc_r_res->SetSelection((int)lgdiff); } else { wxMessageBox(wxT("Please start recording"), _T("Could not auto calibrate"), wxICON_INFORMATION | wxOK); } } void MainFrame::OnAutoSincClick(wxCommandEvent& WXUNUSED(event)) { if (button_sinc->GetValue()) { window_osc->SetInterp(CtrlOScope::Interpolation::SINC); } else { window_osc->SetInterp(CtrlOScope::Interpolation::LINE); } } static const int frm_low = 20; void MainFrame::CalcFreqResponse() { /* periodically called by OnTimer * delays for measuring work by waiting for the next call */ if (frm_istep <= (int)frm_ipoints) { float freq = frm_low * pow(10.0, 3.0 * frm_istep / frm_ipoints); if (0 == frm_measure) { // play new frequency e.g. from 20Hz to 20kHz m_RWAudio->PlaySetGenerator(freq, freq, RWAudio::SINE, RWAudio::SINE, pow(10, slide_l_am->GetValue() / 20.0), pow(10, slide_r_am->GetValue() / 20.0)); wxString bla; bla.Printf(wxT("Frequency : %.1f "), freq); window_1_frm->ShowUserText(bla, 100, 20); } if (1 == frm_measure) g_SpeBufferChanged = false; // now get audio data if (g_SpeBufferChanged.load() && frm_measure > 1) { // new audio data has arrived double l_rms = 0; double r_rms = 0; // compute RMS value in the grabbed wave and store it as a result for (unsigned long int ii = 0; ii < m_SpeBufferLength; ii++) { l_rms += g_SpeBuffer_Left[ii] * g_SpeBuffer_Left[ii]; r_rms += g_SpeBuffer_Right[ii] * g_SpeBuffer_Right[ii]; } frm_freqs.Add(freq); frm_lgains.Add(sqrt(l_rms / m_SpeBufferLength)); frm_rgains.Add(sqrt(r_rms / m_SpeBufferLength)); frm_measure = -1; // zero after increment frm_istep++; } frm_measure++; } else { frm_running = false; window_1_frm->ShowUserText(wxString(""), 0, 0); button_frm_start->SetValue(false); button_frm_start->SetLabel(_T("Start")); SendGenSettings(); // stop generator } } void MainFrame::DrawFreqResponse(void) { wxArrayDouble left, right; double tmpval; left.Clear(); right.Clear(); for (unsigned int i = 0; i < frm_freqs.size(); i++) { tmpval = frm_lgains[i]; left.Add(20.0 * log10(tmpval)); tmpval = frm_rgains[i]; right.Add(20.0 * log10(tmpval)); } window_1_frm->SetTrack1(left); window_1_frm->SetTrack2(right); window_1_frm->SetTrackX(frm_freqs); } void MainFrame::DrawOscilloscope(void) { osc_lmagns.Clear(); osc_rmagns.Clear(); osc_times.Clear(); double trigger_edge; double trigger_level = 0.0; unsigned long int xtrig = 0; // point where the trigger occures const double range[] = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000}; double range_div = 1 / range[choice_osc_l_res->GetSelection()]; double shft_val = 20.0 * (choice_osc_l_off->GetSelection() - 5) / 100.0; double range_div2 = 1 / range[choice_osc_r_res->GetSelection()]; double shft_val2 = 20.0 * (choice_osc_r_off->GetSelection() - 5) / 100.0; double hysteresis_level = range_div / 20.0; // triggering - re-done a little bit, more or less ... trigger_edge = (0 == choice_osc_trig_edge->GetSelection()) ? 1.0 : -1.0; switch (choice_osc_trig_source->GetSelection()) { case 1: // left channel - look for the value under hysteresis point and then over 0 while (xtrig < m_OscBufferLength) { if ((trigger_level - hysteresis_level) > (trigger_edge * g_OscBuffer_Left[xtrig])) { break; } xtrig++; } while (xtrig < m_OscBufferLength) { if (trigger_level < (trigger_edge * g_OscBuffer_Left[xtrig])) { break; } xtrig++; } break; case 2: // right channel while (xtrig < m_OscBufferLength) { if ((trigger_level - hysteresis_level) > (trigger_edge * g_OscBuffer_Right[xtrig])) { break; } xtrig++; } while (xtrig < m_OscBufferLength) { if (trigger_level < (trigger_edge * g_OscBuffer_Right[xtrig])) { break; } xtrig++; } break; default: // no trigger break; } if (xtrig < m_OscBufferLength) { unsigned long int finalBufferPoint = xtrig + m_OscBufferLength; // wrapped exactly for the OScopeCtrl X range if (finalBufferPoint > 2.0 * m_OscBufferLength) { finalBufferPoint = (unsigned long)(2.0 * m_OscBufferLength); } for (int i = 0; i + xtrig < finalBufferPoint; i++) { osc_lmagns.Add(g_OscBuffer_Left[i + xtrig] / range_div - shft_val); osc_rmagns.Add(g_OscBuffer_Right[i + xtrig] / range_div2 - shft_val2); osc_times.Add((double)i / m_SamplingFreq); } } window_osc->SetTrack1(osc_lmagns); window_osc->SetTrack2(osc_rmagns); window_osc->SetTrackX(osc_times); } double MainFrame::calc_dc(const float* data, int size) { double dc = 0.0; for (int i = 0; i < size; i++) { dc += data[i]; } return dc / size; } void MainFrame::DrawSpectrum(void) { double *realin, *realout, *imagout, *windowf; int nsampl = m_SpeBufferLength; realin = (double*)malloc(nsampl * sizeof(double)); realout = (double*)malloc(nsampl * sizeof(double)); imagout = (double*)malloc(nsampl * sizeof(double)); windowf = (double*)malloc(nsampl * sizeof(double)); spe_freqs.Clear(); spe_lmagns.Clear(); spe_rmagns.Clear(); // calculate window const double multiplier = 2 * M_PI / nsampl; switch (choice_fft->GetSelection()) { case 1: // Hanning for (int i = 0; i < nsampl; i++) { windowf[i] = 2 * (0.5 + -0.5 * cos(i * multiplier)) / (double)nsampl; } break; case 2: // Blackman for (int i = 0; i < nsampl; i++) { windowf[i] = 2.4 * (0.42 - 0.5 * cos(multiplier * i) + 0.08 * cos(2 * multiplier * i)) / (double)nsampl; } break; case 3: // Blackman Harris minimum 4 term for (int i = 0; i < nsampl; i++) { windowf[i] = 2.63 * (0.35875 + -0.48829 * cos(i * multiplier) + 0.14128 * cos(i * multiplier * 2) + -0.01168 * cos(i * multiplier * 3)) / (double)nsampl; } break; default: // Rectangle for (int i = 0; i < nsampl; i++) { windowf[i] = 1.0 / (double)nsampl; } break; } double dval = 0.0; double dmax = 0.0; double dval_db = 0.0; // left channel double offsetL = calc_dc(g_SpeBuffer_Left, nsampl); for (int i = 0; i < nsampl; i++) { // copy and apply window realin[i] = (g_SpeBuffer_Left[i] - offsetL) * windowf[i]; } if (fft_double(nsampl, 0, realin, NULL, realout, imagout)) { // use only up to nsampl/2 and skip DC for (int i = 1; i < nsampl / 2; i++) { // multiply amplitude by 2 to compensate dval = 2 * sqrt(realout[i] * realout[i] + imagout[i] * imagout[i]); m_SMASpeLeft->AddVal(i, dval); dval_db = 20.0 * log10(m_SMASpeLeft->GetSMA(i)); spe_lmagns.Add(dval_db); } } else { /* wrong computation */ for (int i = 0; i < nsampl / 2; i++) { spe_lmagns.Add(-150); } } /* find frequency index with highest amplitude */ int fmax = 0; for (int i = 0; i < nsampl / 2; i++) { if (m_SMASpeLeft->GetSMA(i) > dmax) { dmax = m_SMASpeLeft->GetSMA(i); fmax = i; } } /* use that frequency as base and calculate distortion * but only if the magnitude is above -90 db */ double freq = 0.0; double thd = 0.0; double thdval[10] = {1.0E-6}; if (fmax > 0 && dmax > 0.00003) { freq = (double)fmax * m_SamplingFreq / nsampl; for (int i = 0; i < 10; i++) { int j = fmax * (i + 1); if (j < nsampl / 2) thdval[i] = m_SMASpeLeft->GetSMA(j); else thdval[i] = 0.0; } thd = 100 * (thdval[1] + thdval[2] + thdval[3] + thdval[4] + thdval[5] + thdval[6] + thdval[7] + thdval[8] + thdval[9]) / thdval[0]; } /* display base frequency, magnitude and distortion */ wxString freqency; freqency.Printf(wxT("Frequency : %.1lf Hz, Magnitude: %.1lf dB, THD : %lf%%, Avg: %d/%d"), freq, 20.0 * log10(thdval[0]), thd, m_SMASpeLeft->GetNumSummed(1), m_SMASpeLeft->GetNumAverage()); frame_1_statusbar->SetStatusText(freqency); // right channel double offsetR = calc_dc(g_SpeBuffer_Right, nsampl); for (int i = 0; i < nsampl; i++) { // copy and apply window realin[i] = (g_SpeBuffer_Right[i] - offsetR) * windowf[i]; } if (fft_double(nsampl, 0, realin, NULL, realout, imagout)) { // use only up to nsampl/2 and skip DC for (int i = 1; i < nsampl / 2; i++) { dval = 2 * sqrt(realout[i] * realout[i] + imagout[i] * imagout[i]); m_SMASpeRight->AddVal(i, dval); dval_db = (20.0 * log10(m_SMASpeRight->GetSMA(i))); spe_rmagns.Add(dval_db); } } else { /* wrong computation */ for (int i = 0; i < nsampl / 2; i++) { spe_rmagns.Add(-150); } } double fbase = (double)m_SamplingFreq / nsampl; // frequencies without DC for (int i = 1; i < nsampl / 2; i++) { spe_freqs.Add(fbase * i); } window_1_spe->SetTrack1(spe_lmagns); window_1_spe->SetTrack2(spe_rmagns); window_1_spe->SetTrackX(spe_freqs); switch (choice_fftrx->GetSelection()) { case 1: window_1_spe->SetXRange(20, 20000, 1); break; case 2: window_1_spe->SetXRange(10, 100000, 1); break; default: window_1_spe->SetXRange(2, 2000, 1); break; } free(realin); free(realout); free(imagout); free(windowf); } void MainFrame::OnTimer(wxTimerEvent& WXUNUSED(event)) { bool refresh = false; if (g_OscBufferChanged.load() && button_osc_start->GetValue()) { DrawOscilloscope(); g_OscBufferChanged = false; refresh = true; } if (frm_running) { CalcFreqResponse(); DrawFreqResponse(); refresh = true; } if (g_SpeBufferChanged.load() && button_spe_start->GetValue()) { DrawSpectrum(); refresh = true; g_SpeBufferChanged = false; } if (refresh) { Refresh(); Update(); } } void MainFrame::OnOscXScaleChanged(wxCommandEvent& WXUNUSED(event)) { sweep_div = wxAtoi(choice_osc_swp->GetString(choice_osc_swp->GetSelection())); setoscbuf(); window_osc->SetXRange(0, sweep_div * 10E-6, 0); m_SpeBufferLength = wxAtoi(choice_fftlength->GetString(choice_fftlength->GetSelection())); m_RWAudio->ChangeBufLen((unsigned long)(2.0 * m_OscBufferLength), m_SpeBufferLength); // we need bigger buffer because of synchronization m_SMASpeLeft->SetNumRecords(m_SpeBufferLength >> 1); m_SMASpeRight->SetNumRecords(m_SpeBufferLength >> 1); g_SpeBufferChanged = false; g_OscBufferChanged = false; } void MainFrame::OnFFTAvgChanged(wxCommandEvent& WXUNUSED(event)) { int numAverage = wxAtoi(choice_fftavg->GetString(choice_fftavg->GetSelection())); m_SMASpeLeft->SetNumAverage(numAverage); m_SMASpeRight->SetNumAverage(numAverage); } void MainFrame::OnFFTScaleChanged(wxCommandEvent& WXUNUSED(event)) { double ref; double dbDiv; choice_spe_ref->GetString(choice_spe_ref->GetSelection()).ToDouble(&ref); choice_spe_dbdiv->GetString(choice_spe_dbdiv->GetSelection()).ToDouble(&dbDiv); double lo = ref - 10 * dbDiv; window_1_spe->SetYRange(lo, ref, 0); } void MainFrame::OnSpanStart(wxCommandEvent& WXUNUSED(event)) { if (button_spe_start->GetValue()) { m_SMASpeLeft->SetNumRecords(m_SpeBufferLength >> 1); m_SMASpeRight->SetNumRecords(m_SpeBufferLength >> 1); button_spe_start->SetLabel(_T("Stop")); } else { button_spe_start->SetLabel(_T("Start")); } } void MainFrame::OnGenStart(wxCommandEvent& WXUNUSED(event)) { if (button_gen_start->GetValue()) { button_gen_start->SetLabel(_T("Stop")); } else { button_gen_start->SetLabel(_T("Start")); } SendGenSettings(); } void MainFrame::OnOscStart(wxCommandEvent& WXUNUSED(event)) { if (button_osc_start->GetValue()) { button_osc_start->SetLabel(_T("Stop")); } else { button_osc_start->SetLabel(_T("Start")); } } void MainFrame::OnFrmStart(wxCommandEvent& WXUNUSED(event)) { if (button_frm_start->GetValue()) { long ip; button_frm_start->SetLabel(_T("Stop")); wxString tpoints = text_ctrl1_frm->GetValue(); tpoints.ToLong(&ip, 10); if (ip > 120) ip = 120; if (ip < 1) ip = 1; frm_ipoints = (int)ip; frm_istep = 0; frm_freqs.Clear(); frm_lgains.Clear(); frm_rgains.Clear(); frm_measure = 0; frm_running = true; } else { frm_running = false; button_frm_start->SetValue(false); button_frm_start->SetLabel(_T("Start")); SendGenSettings(); // stop generator } } void MainFrame::OnGeneratorChanged(wxCommandEvent& WXUNUSED(event)) { if (checkbox_gen_sync->IsChecked()) { slide_r_fr->Enable(false); label_gen_wave_r->Enable(false); txt_freq_r->Enable(false); choice_r_wav->Enable(false); label_gen_freq_r->Enable(false); } else { slide_r_fr->Enable(true); choice_r_wav->Enable(true); txt_freq_r->Enable(true); label_gen_wave_r->Enable(true); label_gen_freq_r->Enable(true); } if (button_gen_start->GetValue()) { SendGenSettings(); } } void MainFrame::OnOscChoiceChanged(wxCommandEvent& WXUNUSED(event)) {} void MainFrame::OnGenScrollLChanged(wxScrollEvent& WXUNUSED(event)) { wxString bla; bla.Printf(wxT("%.1f"), floor(20.0 * pow(10.0, 3.0 * slide_l_fr->GetValue() / 200.0))); txt_freq_l->SetValue(bla); if (button_gen_start->GetValue()) { SendGenSettings(); } } void MainFrame::OnGenScrollRChanged(wxScrollEvent& WXUNUSED(event)) { wxString bla; bla.Printf(wxT("%.1f"), floor(20.0 * pow(10.0, 3.0 * slide_r_fr->GetValue() / 200.0))); txt_freq_r->SetValue(bla); if (button_gen_start->GetValue()) { SendGenSettings(); } } void MainFrame::OnGenScrollChanged(wxScrollEvent& WXUNUSED(event)) { wxString bla; bla.Printf(wxT("Amplitude: %d dB"), slide_l_am->GetValue()); label_gen_ampl_l->SetLabel(bla); bla.Printf(wxT("Amplitude: %d dB"), slide_r_am->GetValue()); label_gen_ampl_r->SetLabel(bla); if (button_gen_start->GetValue()) { SendGenSettings(); } } void MainFrame::SendGenSettings() { float freq_l, freq_r; double phas2; double doubleToFreq; float gain_l, gain_r; if ((checkbox_l_en->IsChecked()) && (button_gen_start->GetValue())) { gain_l = 1.0 * pow(10, slide_l_am->GetValue() / 20.0); } else { gain_l = 0.0; } if ((checkbox_r_en->IsChecked()) && (button_gen_start->GetValue())) { gain_r = 1.0 * pow(10, slide_r_am->GetValue() / 20.0); } else { gain_r = 0.0; } text_gen_sync->GetValue().ToDouble(&phas2); txt_freq_l->GetValue().ToDouble(&doubleToFreq); freq_l = (float)doubleToFreq; txt_freq_r->GetValue().ToDouble(&doubleToFreq); freq_r = (float)doubleToFreq; RWAudio::Waveform shapeleft = static_cast(choice_l_wav->GetSelection()); RWAudio::Waveform shaperight = static_cast(choice_r_wav->GetSelection()); if (checkbox_gen_sync->IsChecked()) { freq_r = freq_l; shaperight = shapeleft; } else { phas2 = 0.0; } m_RWAudio->PlaySetGenerator(freq_l, freq_r, shapeleft, shaperight, gain_l, gain_r); m_RWAudio->PlaySetPhaseDiff(phas2 * 3.14159 / 180.0); // should be in degrees now } void MainFrame::OnSelectSndCard(wxCommandEvent& WXUNUSED(event)) { unsigned int recdev, pldev; RWAudioDevList playDevList; RWAudioDevList recordDevList; unsigned int newFrequency = m_SamplingFreq; AIStreamSettings m_StreamSettings; m_RWAudio->GetRWAudioDevices(&playDevList, &recordDevList); AudioInterfaceDialog dlg(this); m_StreamSettings.playDev = m_PlayDev; m_StreamSettings.recordDev = m_RecordDev; m_StreamSettings.freq = m_SamplingFreq; dlg.SetDevices(recordDevList, playDevList, m_StreamSettings); if (wxID_OK == dlg.ShowModal()) { // send settings to RWAudio dlg.GetSelectedDevs(&recdev, &pldev, &newFrequency); m_RWAudio->SetSndDevices(recdev, pldev, newFrequency); m_PlayDev = pldev; m_RecordDev = recdev; m_SamplingFreq = newFrequency; m_SMASpeLeft->SetNumRecords(m_SpeBufferLength >> 1); m_SMASpeRight->SetNumRecords(m_SpeBufferLength >> 1); setoscbuf(); m_RWAudio->ChangeBufLen((unsigned long)(2.0 * m_OscBufferLength), m_SpeBufferLength); // we need bigger buffer because of synchronization g_OscBufferChanged = false; } } void MainFrame::OnTxtFreqLChanged(wxCommandEvent& WXUNUSED(event)) { if (button_gen_start->GetValue()) { SendGenSettings(); } } void MainFrame::OnTxtFreqRChanged(wxCommandEvent& WXUNUSED(event)) { if (button_gen_start->GetValue()) { SendGenSettings(); } } class AudMeSApp : public wxApp { public: virtual bool OnInit(); }; wxIMPLEMENT_APP(AudMeSApp); bool AudMeSApp::OnInit() { wxInitAllImageHandlers(); MainFrame* frame_1 = new MainFrame(NULL, -1, wxT("")); SetTopWindow(frame_1); frame_1->Show(); return true; } audmes-2025.04.05/AudMeS.h000066400000000000000000000137671477556201600147110ustar00rootroot00000000000000// -*- C++ -*- generated by wxGlade 0.3.1 on Wed Sep 29 16:09:00 2004 /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include // begin wxGlade: ::dependencies #include // end wxGlade #include #include "CtrlOScope.h" #include "RWAudio_IO.h" #include "sma_2d.h" #ifndef AUDMES_H #define AUDMES_H #define AUDMES_VERSION_STRING "2025-04-05" class MainFrame : public wxFrame { wxDECLARE_CLASS(MainFrame); wxDECLARE_EVENT_TABLE(); public: // begin wxGlade: MainFrame::ids // end wxGlade MainFrame(wxWindow* parent, int id, const wxString& title, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE); private: // begin wxGlade: MainFrame::methods void set_properties(); void set_custom_props(); void do_layout(); // end wxGlade void setoscbuf(); double calc_dc(const float* data, int size); void OnSpanStart(wxCommandEvent& event); void OnGenStart(wxCommandEvent& event); void OnOscStart(wxCommandEvent& event); void OnFrmStart(wxCommandEvent& event); void OnAboutClick(wxCommandEvent& event); void OnExitClick(wxCommandEvent& event); void OnClose(wxCloseEvent& event); void OnTimer(wxTimerEvent& event); void OnGeneratorChanged(wxCommandEvent& event); void OnGenScrollChanged(wxScrollEvent& event); void OnGenScrollLChanged(wxScrollEvent& event); void OnGenScrollRChanged(wxScrollEvent& event); void OnOscChoiceChanged(wxCommandEvent& event); void SendGenSettings(void); void OnSelectSndCard(wxCommandEvent& event); void OnTxtFreqLChanged(wxCommandEvent& event); void OnTxtFreqRChanged(wxCommandEvent& event); void OnOpenClick(wxCommandEvent& event); void OnSaveClick(wxCommandEvent& event); void OnSaveAsClick(wxCommandEvent& event); void OnLoadFRM(wxCommandEvent& event); void OnSaveFRM(wxCommandEvent& event); void OnSaveSPE(wxCommandEvent& event); void OnSaveOSC(wxCommandEvent& event); void OnAutoCalClick(wxCommandEvent& event); void OnAutoSincClick(wxCommandEvent& event); void OnOscXScaleChanged(wxCommandEvent& event); void OnFFTAvgChanged(wxCommandEvent& event); void OnFFTScaleChanged(wxCommandEvent& event); void DrawFreqResponse(void); void CalcFreqResponse(void); void DrawOscilloscope(void); void DrawSpectrum(void); protected: // begin wxGlade: MainFrame::attributes wxStaticBox* sizer_osc_r_staticbox; wxStaticBox* sizer_osc_l_staticbox; wxStaticBox* sizer_osc_h_staticbox; wxStaticBox* sizer_gen_r_staticbox; wxStaticBox* sizer_gen_l_staticbox; wxStaticBox* sizer_spe_fft_staticbox; wxStaticBox* sizer_spe_disp_staticbox; wxStaticBox* sizer_spe_scale_staticbox; wxMenuBar* frame_1_menubar; wxStatusBar* frame_1_statusbar; wxCheckBox* checkbox_l_en; wxStaticText* label_gen_wave_l; wxChoice* choice_l_wav; wxStaticText* label__gen_freq_l; wxSlider* slide_l_fr; wxStaticText* label_gen_ampl_l; wxSlider* slide_l_am; wxCheckBox* checkbox_r_en; wxStaticText* label_gen_wave_r; wxChoice* choice_r_wav; wxStaticText* label_gen_freq_r; wxSlider* slide_r_fr; wxStaticText* label_gen_ampl_r; wxSlider* slide_r_am; wxToggleButton* button_gen_start; wxPanel* notebook_1_gen; CtrlOScope* window_osc; wxStaticText* label_osc_time; wxChoice* choice_osc_swp; wxStaticText* label_osc_div_l; wxChoice* choice_osc_l_res; wxStaticText* label_osc_off_l; wxChoice* choice_osc_l_off; wxStaticText* label_8; wxChoice* choice_osc_trig_source; wxStaticText* label_osc_div_r; wxChoice* choice_osc_r_res; wxStaticText* label_osc_off_r; wxChoice* choice_osc_r_off; wxStaticText* label_osc_trig; wxChoice* choice_osc_trig_edge; wxToggleButton* button_osc_start; wxPanel* notebook_1_osc; wxStaticText* label_5; wxChoice* choice_fft; wxStaticText* label_9; wxChoice* choice_fftlength; wxStaticText* label_rx; wxChoice* choice_fftrx; wxStaticText* label_avg; wxChoice* choice_fftavg; wxStaticText* label_spe_ref; wxChoice* choice_spe_ref; wxStaticText* label_spe_dbdiv; wxChoice* choice_spe_dbdiv; CtrlOScope* window_1_spe; wxToggleButton* button_spe_start; wxPanel* notebook_1_spe; wxNotebook* notebook_1; wxTextCtrl* txt_freq_l; wxTextCtrl* txt_freq_r; wxCheckBox* checkbox_gen_sync; wxStaticText* label_gen_sync; wxTextCtrl* text_gen_sync; // end wxGlade wxPanel* notebook_1_frm; wxStaticText* label_1_frm; wxStaticText* label_2_frm; wxTextCtrl* text_ctrl1_frm; wxTextCtrl* text_ctrl2_frm; wxToggleButton* button_frm_start; CtrlOScope* window_1_frm; RWAudio* m_RWAudio; wxTimer* m_timer; SMA_2D* m_SMASpeLeft; SMA_2D* m_SMASpeRight; bool frm_running; int frm_ipoints; int frm_istep; int frm_measure; double sweep_div; wxArrayDouble osc_times; wxArrayDouble osc_lmagns; wxArrayDouble osc_rmagns; wxArrayDouble spe_freqs; wxArrayDouble spe_lmagns; wxArrayDouble spe_rmagns; wxArrayDouble frm_freqs; wxArrayDouble frm_lgains; wxArrayDouble frm_rgains; wxButton* button_autocalibrate; wxToggleButton* button_sinc; wxString m_configfilename; std::string m_rtinfo; unsigned long int m_OscBufferLength; unsigned long int m_SpeBufferLength; unsigned int m_PlayDev; unsigned int m_RecordDev; unsigned int m_SamplingFreq; }; #endif // AUDMES_H audmes-2025.04.05/CMakeLists.txt000066400000000000000000000150701477556201600161470ustar00rootroot00000000000000cmake_minimum_required (VERSION 3.10) project(AudMeS) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CPP_SOURCES AudMeS.cpp AudMeS.h CtrlOScope.cpp CtrlOScope.h dlg_audiointerface.cpp dlg_audiointerface.h RWAudio_IO.cpp RWAudio_IO.h sma_2d.cpp sma_2d.h ) set(AM_SOURCES ${CPP_SOURCES} rtaudio/RtAudio.cpp rtaudio/RtAudio.h fourier.c fourier.h ) add_subdirectory (tests) include_directories(.) # for local libfccp # Default build type is Release if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Release") endif() message("Build type is ${CMAKE_BUILD_TYPE}") if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") add_definitions(-D_DEBUG) endif() if (WIN32) set(AM_SOURCES ${AM_SOURCES} audmes.rc) set(wxWidgets_USE_LIBS ON) endif() if (MINGW) execute_process(COMMAND bash -c "wx-config --prefix | tr -d '\n'" OUTPUT_VARIABLE MINGW_DIR) message("wxWidgets dir settings: ${MINGW_DIR}") endif() if (${APPLE}) set(audmes_icon "${CMAKE_CURRENT_SOURCE_DIR}/audmes.icns") set(AM_SOURCES ${audmes_icon} ${AM_SOURCES}) endif() find_package(wxWidgets REQUIRED core base) include("${wxWidgets_USE_FILE}") message("wxWidgets lib settings: ${wxWidgets_LIBRARIES}") find_path(FCCP libfccp/csv.h HINTS . ) if (NOT FCCP) message(FATAL_ERROR "Could not find libfccp, please see README.md.") endif() message("libfccp found ${FCCP}") add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE ${AM_SOURCES}) if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux") add_definitions(-D__LINUX_ALSA__ -D__LINUX_PULSE__ -D__LINUX__ -DHAVE_GETTIMEOFDAY) target_link_libraries(${PROJECT_NAME} ${wxWidgets_LIBRARIES} asound pulse pulse-simple) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) endif() if (${APPLE}) add_definitions(-D__MACOSX_CORE__ -DHAVE_GETTIMEOFDAY) find_library(COREAUDIO_LIB CoreAudio) find_library(COREFOUNDATION_LIB CoreFoundation) target_link_libraries(${PROJECT_NAME} ${wxWidgets_LIBRARIES} ${COREAUDIO_LIB} ${COREFOUNDATION_LIB}) set_source_files_properties(${audmes_icon} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") # Create app Bundle but skip signing set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE TRUE XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist" ) endif() if (MSVC) add_definitions(-D__WINDOWS_WASAPI__ -D__WINDOWS_DS__) set(wxWidgets_EXCLUDE_COMMON_LIBRARIES TRUE) target_link_libraries(${PROJECT_NAME} ${wxWidgets_LIBRARIES} dsound ksuser mfplat mfuuid wmcodecdspuuid winmm ole32) if (EXISTS "${CMAKE_BINARY_DIR}/Release") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${wxWidgets_LIB_DIR}/wxbase30u_vc142_x64.dll" "Release" COMMAND ${CMAKE_COMMAND} -E copy "${wxWidgets_LIB_DIR}/wxmsw30u_core_vc142_x64.dll" "Release") endif() if (EXISTS "${CMAKE_BINARY_DIR}/Debug") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${wxWidgets_LIB_DIR}/wxbase30ud_vc142_x64.dll" "Debug" COMMAND ${CMAKE_COMMAND} -E copy "${wxWidgets_LIB_DIR}/wxmsw30ud_core_vc142_x64.dll" "Debug") endif() endif() if (MINGW) add_definitions(-D__WINDOWS_WASAPI__ -D__WINDOWS_DS__) target_link_libraries( ${PROJECT_NAME} ${wxWidgets_LIBRARIES} dsound ksuser mfplat mfuuid wmcodecdspuuid winmm ole32 ) endif() set(CPACK_PACKAGE_VERSION_MAJOR "2025") set(CPACK_PACKAGE_VERSION_MINOR "04") set(CPACK_PACKAGE_VERSION_PATCH "05") if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux") install(TARGETS ${PROJECT_NAME} DESTINATION "bin") install(FILES "audmes.desktop" DESTINATION "share/applications") install(FILES "net.sourceforge.audmes.audmes.appdata.xml" DESTINATION "share/appdata") install(FILES "README.md" DESTINATION "share/doc/audmes") install(FILES "NEWS" DESTINATION "share/doc/audmes") install(FILES "audmes.png" DESTINATION "share/pixmaps") install(FILES "audmes.png" DESTINATION "share/icons/hicolor/128x128/apps/") set(CPACK_GENERATOR "DEB") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "swwa") set(CPACK_DEBIAN_PACKAGE_SECTION "sound") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES) set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://sourceforge.net/projects/audmes/") endif() if (MINGW) #if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") # message(WARNING "Installation only in **Release** builds supported") #endif() set(CMAKE_INSTALL_PREFIX .) install(TARGETS ${PROJECT_NAME} DESTINATION .) install(FILES "${CMAKE_SOURCE_DIR}/README.md" DESTINATION .) install(FILES "${CMAKE_SOURCE_DIR}/NEWS" RENAME "NEWS.txt" DESTINATION .) # add runtime libraries - static linking does not work with wxWidgets binaries install(FILES "${MINGW_DIR}/bin/libgcc_s_dw2-1.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libstdc++-6.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libwinpthread-1.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/wxbase32u_gcc_custom.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/wxmsw32u_core_gcc_custom.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/liblzma-5.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/zlib1.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libjpeg-8.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libpcre2-16-0.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libpng16-16.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libtiff-6.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libdeflate.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libjbig-0.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libLerc.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libzstd.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libwebp-7.dll" DESTINATION .) install(FILES "${MINGW_DIR}/bin/libsharpyuv-0.dll" DESTINATION .) set(CPACK_GENERATOR "ZIP") endif() if (APPLE) set(APP_NAME ${PROJECT_NAME}) # Install app and libraries install(TARGETS ${APP_NAME} BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION bin COMPONENT Runtime ) set(APPS "\${CMAKE_INSTALL_PREFIX}/${APP_NAME}.app") set(CMAKE_MACOSX_RPATH ON) set(DIRS ${CMAKE_BINARY_DIR}) # Replace absolut library paths with relative to app paths install(CODE "include(BundleUtilities) set(BU_CHMOD_BUNDLE_ITEMS ON) fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\")" ) # Add files to top level dmg directoy install(FILES "${CMAKE_SOURCE_DIR}/README.md" DESTINATION .) install(FILES "${CMAKE_SOURCE_DIR}/NEWS" DESTINATION .) set(CPACK_GENERATOR "DragNDrop") endif() add_custom_target( clang-tidy-check cd ..\; clang-tidy -p ${CMAKE_BINARY_DIR}/compile_commands.json ${CPP_SOURCES} DEPENDS ${CPP_SOURCES} ) add_custom_target(check DEPENDS clang-tidy-check) INCLUDE(CPack) audmes-2025.04.05/COPYING000066400000000000000000000432541477556201600144470ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. audmes-2025.04.05/CtrlOScope.cpp000066400000000000000000000245331477556201600161340ustar00rootroot00000000000000/* CtrlOScope.cpp * * Change Log: * =========== * 20080822 - renamed from OScopeCtrl (because of mess in the project) * - added hard-setting of vertical lines for linear plot * - double buffering !!! * - added event EVT_ERASE_BACKGROUND * - some other small improvements * * verze 0.0.1 * - X osa logaritmicka * - Y osa linearni * - zadna kontrola rozsahu !!!! */ /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "CtrlOScope.h" #include #include CtrlOScope::CtrlOScope(wxWindow* parent, wxString xname, wxString yname) : wxControl(parent, -1, wxDefaultPosition, wxSize(300, 200)) { m_bgColor.Set(0, 0, 0); m_plColor.Set(0, 128, 0); m_trColor.Set(192, 192, 0); m_tr2Color.Set(0, 192, 192); m_whColor.Set(255, 100, 100); m_MaxXValue = 0; m_MinXValue = 0; m_MaxYValue = 0; m_MinYValue = 0; m_LogX = 0; m_LogY = 0; m_YUnit = yname; m_XUnit = xname; m_interp = LINE; m_NumberOfVerticals = 0; m_UserText = wxT(""); m_UserTextPosX = 0; m_UserTextPosY = 0; Bind(wxEVT_SIZE, &CtrlOScope::OnSize, this); Bind(wxEVT_PAINT, &CtrlOScope::OnPaint, this); Bind(wxEVT_ERASE_BACKGROUND, &CtrlOScope::OnEraseBackground, this); } CtrlOScope::~CtrlOScope() {} void CtrlOScope::SetTrack1(wxArrayDouble const& ardbl) { m_points1 = ardbl; } void CtrlOScope::SetTrack2(wxArrayDouble const& ardbl) { m_points2 = ardbl; } void CtrlOScope::SetTrackX(wxArrayDouble const& ardbl) { m_pointsX = ardbl; } void CtrlOScope::SetInterp(CtrlOScope::Interpolation interp) { m_interp = interp; } void CtrlOScope::SetXRange(double dLower, double dUpper, int logrange) { m_MinXValue = dLower; m_MaxXValue = dUpper; m_LogX = logrange; } void CtrlOScope::SetYRange(double dLower, double dUpper, int logrange) { m_MinYValue = dLower; m_MaxYValue = dUpper; m_LogY = logrange; } void CtrlOScope::OnSize(wxSizeEvent& event) { event.Skip(); } void CtrlOScope::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC cdc(this); // device context for painting // here will be the Double Buffer wxRect rec = GetClientRect(); wxBitmap* bmpBlit = new wxBitmap(rec.width, rec.height, 32); wxMemoryDC* memDC = new wxMemoryDC(); // clear the memdc with a certain background color memDC->SelectObject(*bmpBlit); memDC->Clear(); PaintGraph(*memDC); cdc.Blit(rec.x, rec.y, rec.width, rec.height, memDC, 0, 0, wxCOPY); delete bmpBlit; delete memDC; } inline double sinc(double x) { if (x == 0.0) return 1.0; else return sin(x * M_PI) / (x * M_PI); } static void sinc_interpolate(wxArrayDouble& xdata, wxArrayDouble& ydata, wxArrayDouble& xpoints, wxArrayDouble& ypoints) { size_t size = xpoints.size() - 1; auto srate = size / xpoints[size]; for (size_t j = 0; j < size * 5; j++) { size_t i = 0; double sum = 0.0; double tim = 1.0 * j / (srate * 5); while (i <= size) { sum += sinc((tim - xpoints[i]) * srate) * ypoints[i]; i++; } xdata.Add(tim); ydata.Add(sum); } } void CtrlOScope::PaintGraph(wxDC& dc) { wxRect rec = GetClientRect(); wxString bla; int tw, th; double xstep = 0; static const int fSize = 10; /* kresli pozadi - draw background */ dc.SetPen(wxPen(m_bgColor, 1, wxPENSTYLE_SOLID)); dc.SetBrush(wxBrush(m_bgColor, wxBRUSHSTYLE_SOLID)); dc.DrawRectangle(0, 0, rec.width, rec.height); dc.SetPen(wxPen(m_plColor, 1, wxPENSTYLE_DOT)); dc.SetBrush(wxBrush(m_plColor, wxBRUSHSTYLE_SOLID)); dc.SetTextForeground(m_whColor); dc.SetFont(wxFont(wxFontInfo(fSize))); /* spocitat vysku pisma pro spodni odstup a sirky pto odstup zleva */ /* calculate space for legend */ if (!m_YUnit.empty()) { dc.GetTextExtent(m_YUnit, &tw, &th); udist = tw; } bla.Printf(wxT("%.1f"), m_MinYValue); dc.GetTextExtent(bla, &tw, &th); ldist = udist + tw + 8; bla.Printf(wxT("%.1f"), m_MaxYValue); dc.GetTextExtent(bla, &tw, &th); if (ldist < (udist + tw + 8)) { ldist = udist + tw + 8; } if (!m_XUnit.empty()) { dc.GetTextExtent(_T("T"), &tw, &th); udist = th; } dc.GetTextExtent(wxT("0"), &tw, &th); bdist = udist + th + 8; /* tisk legendy - display legend */ dc.SetBrush(wxBrush(m_plColor, wxBRUSHSTYLE_SOLID)); dc.DrawText(m_YUnit, 4, rec.height / 2); dc.DrawText(m_XUnit, rec.width / 2, rec.height - udist); int ydiv = 10; /* number of horizontal lines */ if (m_MaxYValue - m_MinYValue > 19) ydiv = (m_MaxYValue - m_MinYValue) / 10; for (int i = 0; i <= ydiv; i++) { /* kresli vsechny cary - draw the lines*/ double ystep = 1.0 * (rec.height - bdist - tdist) / ydiv; dc.DrawLine(ldist, (int)(rec.height - ystep * i - bdist), rec.width - rdist, (int)(rec.height - ystep * i - bdist)); bla.Printf(wxT("%.1f"), m_MinYValue + (m_MaxYValue - m_MinYValue) * i / ydiv); dc.GetTextExtent(bla, &tw, &th); dc.DrawText(bla, (int)(ldist - tw - 4), (int)(rec.height - ystep * i - bdist - th / 2)); } /* spocitat jak casto se budou kreslit vertikalni cary */ /* vertical lines depending on linear or log scale */ if (m_LogX) { /* draw vertical lines with log distance */ xstep = (rec.width - ldist - rdist) / log10(m_MaxXValue / m_MinXValue); if (m_MinXValue < 1) m_MinXValue = 1; // avoid log10(0) and rounding errors int decade = log10(m_MinXValue); double freq = m_MinXValue; while (freq <= m_MaxXValue) { dc.DrawLine((int)(ldist + xstep * log10(freq / m_MinXValue)), tdist, (int)(ldist + xstep * log10(freq / m_MinXValue)), rec.height - bdist); if (log10(freq) == decade || log10(freq / 2) == decade || log10(freq / 5) == decade) { int cf = (int)freq; int cfk = cf / 1000; if (cfk >= 1) bla.Printf(wxT("%dk"), cfk); else bla.Printf(wxT("%d"), cf); dc.GetTextExtent(bla, &tw, &th); dc.DrawText(bla, (int)(ldist + xstep * log10(freq / m_MinXValue) - tw / 2), rec.height - bdist + 8); } freq += pow(10, decade); if (log10(freq) - 1 >= decade) { decade++; } } } else { /* draw vertical lines with linear distance */ xstep = (double)(rec.width - ldist - rdist) / m_NumberOfVerticals; for (int i = 0; i <= m_NumberOfVerticals; i++) { dc.DrawLine((int)(ldist + xstep * i), tdist, (int)(ldist + xstep * i), rec.height - bdist); double cl = m_MinXValue + (m_MaxXValue - m_MinXValue) * i / m_NumberOfVerticals; int clk = cl / 1000; int clm = cl * 1000; int clu = cl * 1000000; if (clk >= 1) bla.Printf(wxT("%dk"), clk); else if (cl >= 1) bla.Printf(wxT("%d"), (int)cl); else if (clm >= 1) bla.Printf(wxT("%dm"), clm); else bla.Printf(wxT("%du"), clu); dc.GetTextExtent(bla, &tw, &th); if ((xstep * i + ldist) < (tw / 2)) { dc.DrawText(bla, (int)(ldist + xstep * i), rec.height - bdist + 8); } else if ((ldist + xstep * i + tw / 2) < rec.width) { dc.DrawText(bla, (int)(ldist + xstep * i - tw / 2), rec.height - bdist + 8); } else { dc.DrawText(bla, (int)(ldist + xstep * i - tw), rec.height - bdist + 8); } } } /* zobrazit body - draw data */ if (m_pointsX.GetCount() > 0) { // limit drawing region to the graph dc.SetClippingRegion(ldist, tdist, rec.width - ldist - rdist + 1, rec.height - tdist - bdist + 1); size_t ilow = 0, ihigh = 0; // iterate though all X points in the data for (size_t i = 0; i < m_pointsX.GetCount(); i++) { if (m_pointsX.Item(i) < m_MinXValue) { ilow = i; } if (m_pointsX.Item(i) <= m_MaxXValue) { ihigh = i; } } if (ilow > 0) ilow--; if ((int)ihigh < ((int)m_pointsX.GetCount() - 1)) ihigh++; // left channel if (m_interp == SINC) { wxArrayDouble xdata; wxArrayDouble ydata; sinc_interpolate(xdata, ydata, m_pointsX, m_points1); PaintTrack(dc, ilow, ihigh * 5 - 1, xstep, m_trColor, xdata, ydata); xdata.Clear(); ydata.Clear(); } else { PaintTrack(dc, ilow, ihigh, xstep, m_trColor, m_pointsX, m_points1); } // right channel if (m_interp == SINC) { wxArrayDouble xdata; wxArrayDouble ydata; sinc_interpolate(xdata, ydata, m_pointsX, m_points2); PaintTrack(dc, ilow, ihigh * 5 - 1, xstep, m_tr2Color, xdata, ydata); xdata.Clear(); ydata.Clear(); } else { PaintTrack(dc, ilow, ihigh, xstep, m_tr2Color, m_pointsX, m_points2); } } } void CtrlOScope::PaintTrack(wxDC& dc, size_t from, size_t to, double xstep, const wxColor& color, wxArrayDouble& xpoints, wxArrayDouble& ypoints) { dc.SetPen(wxPen(color, 1, wxPENSTYLE_SOLID)); wxRect rec = GetClientRect(); std::vector pv; // iterate trough the data points in range for (size_t i = from; i <= to; i++) { int xpos; if (m_LogX) xpos = ldist + xstep * log10(m_pointsX.Item(i) / m_MinXValue); else xpos = ldist + xpoints.Item(i) * xstep * m_NumberOfVerticals / (m_MaxXValue - m_MinXValue); // find the point in the graph and limit to the graph area double ydatapoint = ypoints.Item(i); if (ydatapoint > m_MaxYValue) ydatapoint = m_MaxYValue; if (ydatapoint < m_MinYValue) ydatapoint = m_MinYValue; double ypoint = rec.height - bdist - (rec.height - bdist - tdist) * (ydatapoint - m_MinYValue) / (m_MaxYValue - m_MinYValue); wxPoint pt = {xpos, (int)ypoint}; pv.push_back(pt); } if (pv.size() > 1) dc.DrawLines(pv.size(), &pv[0]); if (wxT("") != m_UserText) { dc.SetTextForeground(m_whColor); dc.DrawText(m_UserText, m_UserTextPosX, m_UserTextPosY); } } audmes-2025.04.05/CtrlOScope.h000066400000000000000000000052221477556201600155730ustar00rootroot00000000000000// CtrlOScope.h : header file /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef __CTRLOSCOPE_H__ #define __CTRLOSCOPE_H__ #include #include #include class CtrlOScope : public wxControl { public: CtrlOScope(wxWindow* parent, wxString xname = _T("X"), wxString yname = _T("Y")); virtual ~CtrlOScope(); void OnPaint(wxPaintEvent& event); void OnSize(wxSizeEvent& event); void OnEraseBackground(wxEraseEvent& WXUNUSED(event)){}; void SetXRange(double dLower, double dUpper, int logrange); void SetYRange(double dLower, double dUpper, int logrange); enum Interpolation { DOT, LINE, SINC }; void SetTrack1(wxArrayDouble const& ardbl); void SetTrack2(wxArrayDouble const& ardbl); void SetTrackX(wxArrayDouble const& ardbl); void SetInterp(const CtrlOScope::Interpolation interp); void SetNumOfVerticals(int num) { m_NumberOfVerticals = num; }; void ShowUserText(wxString const& text, int xpos, int ypos) { m_UserText = text; m_UserTextPosX = xpos; m_UserTextPosY = ypos; }; protected: wxArrayDouble m_points1; wxArrayDouble m_points2; wxArrayDouble m_pointsX; double m_MaxXValue; double m_MinXValue; double m_MaxYValue; double m_MinYValue; int m_LogX; int m_LogY; wxString m_XUnit; wxString m_YUnit; wxColour m_bgColor; wxColour m_plColor; wxColour m_trColor; wxColour m_tr2Color; wxColour m_whColor; int m_NumberOfVerticals; wxString m_UserText; int m_UserTextPosX; int m_UserTextPosY; Interpolation m_interp; private: void PaintGraph(wxDC& dc); void PaintTrack(wxDC& dc, size_t from, size_t to, double xstep, const wxColor& color, wxArrayDouble& xpoints, wxArrayDouble& ypoints); int ldist = 0; // legend space on the left int rdist = 20; // space on the right int tdist = 10; // space on top int bdist = 0; // legend space on bottom int udist = 0; // unit text space }; #endif // __CTRLOSCOPE_H__ audmes-2025.04.05/Info.plist000066400000000000000000000027041477556201600153570ustar00rootroot00000000000000 BuildMachineOSBuild 15G22010 CFBundleDevelopmentRegion English CFBundleExecutable AudMes CFBundleGetInfoString AudMes version 2025-04-05 LSApplicationCategoryType public.app-category.education CFBundleIconFile audmes CFBundleIdentifier audmes CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString Version 2025-04-05 CFBundleName AudMeS CFBundlePackageType APPL CFBundleShortVersionString 2025-04-05 CFBundleSignature auDy CFBundleSupportedPlatforms MacOSX CFBundleVersion 2025-04-05 NSHighResolutionCapable NSHumanReadableCopyright AudMeS Version 2025-04-05 - GPLv2 NSPrincipalClass NSApplication CFBundleDisplayName AudMes NSMicrophoneUsageDescription Needs audio input audmes-2025.04.05/NEWS000066400000000000000000000135721477556201600141130ustar00rootroot00000000000000News ==== 2025-04-05 ---------- - Add sin(x)/x interpolation in oscilloscope view - Add wobble sine in generator (slightly frequency modulated sine wave) - Add GutHub actions (build pipeline) - Rework oscilloscope channel range to full scale factor - Update dependencies - Refactor code - Fix reversed left and right label in generator 2024-07-28 ---------- - Rearrange oscilloscope view - Normalize vertical settings to +- 1V and adapt settings - Remove DC from Spectrum - Fix clipping region in graph - Add ccpcheck and clang-tidy settings - Refactor code using above tool hints 2024-02-18 ---------- - Refactor interface between AudMeS and CtrlOscope: Pass time or frequency data (y axis) along with magnitude (x axis). - Rewrite graph code taking advantage of the y axis data. - Add save function to oscilloscope and spectrum. - Use time instead of samples in oscilloscope. 2023-10-24 ---------- - Add simple moving average in spectrum view. - Rearrange spectrum view and increase refresh rate. - Remember selected device in configuration window. Code was contributed by user Johannes. 2023-04-09 ---------- - Add mono channel support as requested by users. - Refactor several files. - Drop frequency measurement in oscilloscope - spectrum does it now and can be run in parallel. Gives better resolution and magnitude. 2022-10-09 ---------- - Update README with binaries installation and Visual Studio instructions - Fix frequency response export in Windows - Add Visual Studio and MSVC support - Update RtAudio to 5.2.0 - Increase font size in legend 2022-04-20 ---------- - Display base frequency, magnitude and THD in spectrum. - Add Blackman-Harris window function. - Increase resolution (> 16 bit) using floating point audio data. - Catch more errors to avoid crash. - Compile in DirectSound and WASAPI on Windows. - Use timer loop instead of sleep for frequency response. 2021-11-04 ---------- - Reformat README.md and fix instructions. - Update to latest RtAudio. - Switch to WASAPI on Windows. - Add package creation with CMake. - Set CMake build type to Release as default. - Add build instructions for MacOSX. - Fix Blackman window function. - Display RtAUdio info in About popup. - Compile Linux with Alsa and Pulseaudio support. - Avoid crash when sound card channels are mono or missing. 2021-03-14 ---------- - Use pulseaudio on Linux up to 192000 samples/s. - Catch sound card issues and show message in initialization. - Optimize screen repaint. - Improve graph and legend display. - Add Debian package support in CMake. - Add compiling instructions for Linux and Windows 10. - Add white noise to generator. - Use RMS level in frequency response instead of peak. 2020-09-24 ---------- - Improve FFT interpolation. - Add second channel to frequency response. 2020-07-01 ---------- - Migrate to CMake. - Migrate README to markdown and move change list to NEWS. - Migrated project from subversion to git. 2019-09-25 ---------- - add FFT window functions Hanning and Blackman. - add spectrum scaling according to sample rate. - modify spectrum view 10 Hz - 100kHz, 0 - -100 dB. - fix sound card dialog (available frequencies). 2019-09-22 ---------- - update RtAudio to version 5.1.0 2018-??-?? ---------- - Updated code to compile with current compilers on Linux. - Updated code to compile with recent versions of rtaudio and wxWidgets. - Documented the choice of GNU GPL v2+ as source license. - Added code to save and load frequency response data as CSV. 2008-08-27 ---------- - sound card switching works; but problems when using different sound cards for input and output 2008-08-26 ---------- - changed colors for numbers in scopes - OSC - L channel measures frequency - FFT - "Audio Interface" Dlg extended with sampling frequency - "Audio Interface" Dlg shows available interfaces but settings do not work yet 2008-08-25 ---------- - first Linux port!!! - compilable with wxGTK-2.6.4 wth GTK2 and - UNICODE; a plenty of problems: - GEN gauges do not work as expected - glitches in generator - wxTextCtrls do not react to ENTERs - SA - variable FFT length - SA - right channel is added - SA - averaging of FFT values introduced 2008-08-22 ---------- - renamed OScopeCtrl->CtrlOScope; double buffering and some other features added - #define for switch XScale to microseconds/div - but a lot of problems with strange modulation 2008-08-21 ---------- - first working version of AudMeS with RtAudio driver - MinGW compiler - Select Sound Card dlg does not work; default device is used 2008-08-20 ---------- - rewrite of audio driver started - RtAudio will be used now 2008-08-15 ---------- - added rectangular, triangle, saw generators - added auto calibration of Y scale ; offset is not changing - prepared for load/save of configuration - added menu accelerators 2008-06-06 ---------- - triggering completely changed; currently missing control of trigger level and trigger hysteresis - changed description of X scale and X resolution - in Makefile.gcc changed directory delimiters in "clean" target 2008-06-04 ---------- - Added text controls to set generator frequency more preciously - phase between L and R channels is now in degrees (before it was radians) 2007-07-25 ---------- - Project renamed to AUDio MEasurement System - AudMeS 2007-07-15 ---------- - RWAudio_win - velky prepis modulu - Sweep funguje - netestovano v realnem prostredi 2007-07-12 ---------- - AudioLab.cpp: OSC - Amplituda zmenena na dB - AudioLab.cpp: OSC - synchronizace L a P kanalu - RWAudio.h: pridan parametr faze do PlaySetGenerator() - RWAudio.cpp: pridana glob_phase1 a glob_phase2 2007-03-27 ---------- - audiolab.h: zmena zobrazeni verze - ta je ted v AUDIOLAB_VERSION_STRING - AudioLab.cpp: pridan vyber SND karty - NEDOKONCENO - dlg_audiointerface.cpp: NOVY - dialog vyberu IN/OUT zarizeni - RWAudio_win.cpp: pridano GetPlayDev() a GetRecDev() - RWAudio_win.cpp: slouceni Rekorderu a Playeru - NEDOKONCENO, NEKONZISTENTNI!!! audmes-2025.04.05/README.md000066400000000000000000000232731477556201600146720ustar00rootroot00000000000000# AUDio MEasurement System ## About Multi-platform system for audio measurement through sound card in the PC. Incorporates Generator, Oscilloscope, Fast Fourier Transform, Sweep frequency characteristic. Project page: Source code: Contact: see project page License: GPLv2 (see COPYING) Features: - generator - spectrum analyzer - oscilloscope - frequency response - just frequency stepping Operating systems: Linux, Windows, MacOS Dependencies: - wxWidgets 3.0.x () - RtAudio 5.2 () - is already in git repo - libfccp () - CMake 3.x () ## Installation You can download compiled binaries from SourceForge. You find them under `Files`. You need a stereo input and output on your sound card. ### Windows Download the binary from SourceForge. It comes a a ZIP archive. Extract the archive into a directory of your choice. Create a shortcut for AudMeS.exe to your desktop. To run, double-click on the shortcut icon. ### MacOS Download the binary from SourceForge. It comes as a disk image (dmg). Mount the disk image by double-clicking it. Drag and drop the app to /Applications. To run, use LaunchPad and click on the AudMeS icon. ### Linux Go to FlatHub , search for `audmes` and install it. Download the binary from SourceForge. It comes as a Flatpak or a Debian package (deb). Install the Debian package with `dpkg -i`. The flatpak can be installed with `flatpak install`. To run, click on the AudMeS icon when you select "Show Applications". ## Troubleshooting If a popup appears about sound card issues, make sure you have stereo input and output available. You may need to plug in a cable. If you see messages like `WARNING **: invalid source position for vertical gradient` then your Gnome Theme has a bug. It is annoying but harmless. On Debian the issue disappears when using e.g. `materia-gtk-theme`. If you get an error like `wxbase30ud_gcc810.dll not found` (note the "d_") when running AudMeS.exe, you compiled in Debug mode. Run cmake again with "-DCMAKE_BUILD_TYPE=Release". ## Compiling on Linux The following instructions are for Debian 12, Ubuntu and similar. Install the basic development tools and dependencies sudo apt install build-essential git cmake sudo apt install libwxgtk3.2-dev sudo apt install libfccp-dev libpulse-dev libasound2-dev Fetch the source git clone https://git.code.sf.net/p/audmes/git audmes-git Compile cd audmes-git mkdir build && cd build cmake .. cmake --build . Test the result ./AudMeS Package and install cmake --build . --target package sudo dpkg -i AudMeS-xxxx.xx.xx-Linux.deb ## Compiling on Windows 11 with Msys2 Download Msys2 from Run `mingw32` terminal and install packages: pacman -S git mingw-w64-i686-gcc mingw-w64-i686-cmake mingw-w64-i686-make pacman -S mingw-w64-i686-wxwidgets3.2-msw Do not install packages for other than i686 targets. Get code: mkdir projects git clone https://git.code.sf.net/p/audmes/git audmes cd audmes Clone libfccp: git clone https://github.com/ben-strasser/fast-cpp-csv-parser libfccp Compiling: mkdir build cd build cmake .. cmake --build . Run the program: AudMeS.exe Package into a ZIP: cmake --build . --target package The ZIP archive contains the executable, libraries and text files for distribution. ## Compiling on Windows 10 with MinGW Get the following software: MinGW: - Get `mingw-w64-install.exe` from Setup MinGW to i686 (32 bit) version: Version 8.1.0 Arch i686 Threads posix Exceptions sjlj Build 0 If you have a previous MinGW installation, say in "C:\MinGW", take care not to mix the two. See setting the path below or simply uninstall the previous installation. Git: - Get `Git-2.26.2-64-bit.exe` from Cmake: - Get `cmake-3.17.2-win64-x64.msi` from The next instructions assume that you use git bash (not command.exe). Use the desktop shortcut "Git bash" you get after after installing git. Create a directory to store the project. mkdir $ENV{HOMEDRIVE}$ENV{HOMEPATH}/projects WxWidgets: - Get `wxMSW-3.0.5_gcc810_Dev.7z` and `wxWidgets-3.0.5-headers.7z` from Extract the archives to `$ENV{HOMEDRIVE}$ENV{HOMEPATH}/projects/wx3.0.5`. CMake expects the libraries in `...\projects\wx3.0.5\lib\gcc_dll`. Copy or rename `...\projects\wx3.0.5\lib\gcc810_dll`. Clone audmes: cd $ENV{HOMEDRIVE}$ENV{HOMEPATH}/projects git clone https://git.code.sf.net/p/audmes/git audmes-git Clone libfccp: git clone https://github.com/ben-strasser/fast-cpp-csv-parser libfccp Compiling: Make sure you have mingw and cmake in your bash path. You need to add the following using the windows "System Properties->Environment Variables" window. "C:\Program Files (x86)\mingw-w64\i686-8.1.0-posix-sjlj-rt_v6-rev0\mingw32\bin" "C:\Program Files\CMake\bin" or add it to the path in the bash profile. Compile 32bit Windows version (git bash in source directory): cd $ENV{HOMEDRIVE}$ENV{HOMEPATH}/projects/audmes-git mkdir build && cd build cmake .. -G "MinGW Makefiles" mingw32-make Install libraries: cp /c/Program\ Files\ \(x86\)/mingw-w64/i686-8.1.0-posix-sjlj-rt_v6-rev0/mingw32/bin/libwinpthread-1.dll . cp /c/Program\ Files\ \(x86\)/mingw-w64/i686-8.1.0-posix-sjlj-rt_v6-rev0/mingw32/bin/libstdc++-6.dll . cp /c/Program\ Files\ \(x86\)/mingw-w64/i686-8.1.0-posix-sjlj-rt_v6-rev0/mingw32/bin/libgcc_s_sjlj-1.dll . cp ../../wx3.0.5/lib/gcc810_dll/wxbase30u_gcc810.dll . cp ../../wx3.0.5/lib/gcc810_dll/wxmsw30u_core_gcc810.dll . Alternative to the above: cmake --build . --target install Run the program: AudMeS.exe Package into a ZIP: cmake --build . --target package The ZIP archive contains the executable, libraries and text files for distribution. ## Compiling on Windows 10 with Visual Studio 2019 Download Visual Studio 2019 from Microsoft. The (free) Visual Studio 2019 Community edition is sufficient. It comes with the necessary C++ compilers, SDK, git client and CMake plugin. Cmake: - Get `cmake-3.17.2-win64-x64.msi` from WxWidgets: - Get `wxMSW-3.0.5_vc142_Dev.7z` and `wxWidgets-3.0.5-headers.7z` from Extract the archives to `$ENV{HOMEDRIVE}$ENV{HOMEPATH}/projects/wx3.0.5`. Use Visual Studio to clone the project. Create a CMake configuration `x86-release`. { "configurations": [ { "name": "x86-Release", "generator": "Ninja", "configurationType": "Release", "buildRoot": "${projectDir}\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x86" ] } ] } Now you can build AudMeS.exe with Visual Studio by selecting "Build -> Build All". ## Compiling on MacOSX Xcode: - Install Xcode from the App Store. It comes with the necessary compilers and tools for MacOSX. Tool to fix paths in App: `install_name_tool`. The MacOSX equivalent for Linux `ldd` is `otool`. CMake: - Download "cmake-3.16.0-rc3-Darwin-x86_64.dmg" from Homebrew: - Install Homebrew first and then use `brew` to install wxWidgets Turn off analytics brew analytics off You may want to get optional install git tools brew install git brew install git-gui wxWidgets: Now use `brew` to get wxWidgets brew install wxwidgets Brew will get version 3.1 which is fine. On MacOSX wxWidgets 3.0 has issues with AudMeS. AudMeS source: mkdir projects && cd projects git clone https://git.code.sf.net/p/audmes/git audmes-git cd audmes-git Libfccp: Clone into projects/audmes-git/libfccp git clone https://github.com/ben-strasser/fast-cpp-csv-parser libfccp Compile on command line: mkdir build && cd build cmake .. cmake --build . During compilation some deprecation warnings appear which can be ignored. Now you can run the result `AudMeS.app` using the Finder. It will ask for permission to access the microphone. Please allow it. Package AudMeS as disk image: cmake --build . --target package The result is `AudMeS-xxxx.xx.xx-Darwin.dmg` which can be mounted using the Finder and allows to install the app in the usual way. Using Xcode as IDE: To prepare the project for Xcode and debugging you can use Cmake. cmake .. -G Xcode -DCMAKE_BUILD_TYPE=Debug Then open the project in XCode. You may need to remove all in the `build` directory first before running cmake. ## Flatpak Check the flatpak data in audmes repository. vi net.sourceforge.audmes.audmes.appdata.xml Fork the project for Flathub. git checkout https://github.com/swwa/net.sourceforge.audmes.audmes.git cd net.sourceforge.audmes.audmes Edit commit hash in the manifest. vi net.sourceforge.audmes.audmes.json Build the flatpak and test it. flatpak-builder --repo=repo --force-clean build net.sourceforge.audmes.audmes.json flatpak build-bundle repo audmes.flatpak net.sourceforge.audmes.audmes flatpak --user install audmes.flatpak flatpak run net.sourceforge.audmes.audmes Now make a pull request on github. Test the build from the build-bot. As Flatpak administrator merge pull request and test the build. audmes-2025.04.05/RWAudio_IO.cpp000066400000000000000000000332041477556201600160130ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // RWAudio_IO.cpp: impementation of audio interface // ////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include // for WXUNUSED #include "RWAudio_IO.h" #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // extern variables from AudMeS.cpp extern float *g_OscBuffer_Left; extern float *g_OscBuffer_Right; extern long int g_OscBufferPosition; extern float *g_SpeBuffer_Left; extern float *g_SpeBuffer_Right; extern long int g_SpeBufferPosition; extern std::atomic g_OscBufferChanged; extern std::atomic g_SpeBufferChanged; static double ph_wobble = 0.0; /* * pseudo noise generator - linear feedback shift register * derived from https://en.wikipedia.org/wiki/Linear-feedback_shift_register */ bool lfsr16() { uint16_t bit; /* Must be 16-bit to allow bit<<15 later in the code */ static uint16_t lfsr = 1; /* Must not be 0 */ /* taps: 16 14 13 11; feedback polynomial: x^16 + x^14 + x^13 + x^11 + 1 */ bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1u; lfsr = (lfsr >> 1) | (bit << 15); return bit; } /* * callback function to catch runtime errors */ void catcherr(RtAudioError::Type WXUNUSED(type), const std::string &errorText) { std::cerr << '\n' << errorText << '\n' << std::endl; } /* * callback function to fetch audio input and generate tones */ int inout(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double WXUNUSED(streamTime), RtAudioStreamStatus status, void *data) { RWAudio *aRWAudioClass = (RWAudio *)data; unsigned i; if (status) std::cerr << "Audio stream over/underflow detected." << std::endl; // copy input buffer into two L/R channels if (aRWAudioClass->m_Buflen_Changed) { aRWAudioClass->m_Buflen_Changed = false; free(g_OscBuffer_Left); free(g_OscBuffer_Right); free(g_SpeBuffer_Left); free(g_SpeBuffer_Right); g_OscBufferPosition = 0; g_SpeBufferPosition = 0; g_OscBuffer_Left = (float *)malloc(aRWAudioClass->m_OscBufferLen * sizeof(float)); g_OscBuffer_Right = (float *)malloc(aRWAudioClass->m_OscBufferLen * sizeof(float)); g_SpeBuffer_Left = (float *)malloc(aRWAudioClass->m_SpeBufferLen * sizeof(float)); g_SpeBuffer_Right = (float *)malloc(aRWAudioClass->m_SpeBufferLen * sizeof(float)); } float *inBuf = (float *)inputBuffer; // make a copy for oscilloscope if (!g_OscBufferChanged.load()) { for (i = 0; i < nBufferFrames; i++) { // copy audio signal to fft real component. g_OscBuffer_Left[g_OscBufferPosition] = *inBuf++; if (aRWAudioClass->m_channels_in > 1) g_OscBuffer_Right[g_OscBufferPosition] = *inBuf++; else g_OscBuffer_Right[g_OscBufferPosition] = 0; g_OscBufferPosition++; // if the buffer is over we have to pick the data and then circullary fill the next one if (g_OscBufferPosition == aRWAudioClass->m_OscBufferLen) { g_OscBufferPosition = 0; g_OscBufferChanged = true; break; } } } inBuf = (float *)inputBuffer; // make a copy for spectrum analyzer if (!g_SpeBufferChanged.load()) { for (i = 0; i < nBufferFrames; i++) { // copy audio signal to fft real component. g_SpeBuffer_Left[g_SpeBufferPosition] = *inBuf++; if (aRWAudioClass->m_channels_in > 1) g_SpeBuffer_Right[g_SpeBufferPosition] = *inBuf++; else g_SpeBuffer_Right[g_SpeBufferPosition] = 0; g_SpeBufferPosition++; // if the buffer is over we have to pick the data and then circullary fill the next one if (g_SpeBufferPosition == aRWAudioClass->m_SpeBufferLen) { g_SpeBufferPosition = 0; g_SpeBufferChanged = true; break; } } } // fill output buffer float *outBuf = (float *)outputBuffer; #ifdef _DEBUG // fprintf(ddbg, "\n Frames: %d\n ", nBufferFrames); #endif /* calculate the wave form according to the selected shape */ for (i = 0; i < nBufferFrames; i++) { double y = 0; double y2 = 0; bool noise = lfsr16(); /* left channel */ switch (aRWAudioClass->m_genShape_l) { case RWAudio::RECT: if (aRWAudioClass->m_genPhase_l < M_PI) { y = 1.0; } else { y = -1.0; } break; case RWAudio::SAW: y = (aRWAudioClass->m_genPhase_l - M_PI) / M_PI; break; case RWAudio::TRI: if (aRWAudioClass->m_genPhase_l < M_PI) { y = 2 * (aRWAudioClass->m_genPhase_l - M_PI / 2) / M_PI; } else { y = 2 * (3 * M_PI / 2 - aRWAudioClass->m_genPhase_l) / M_PI; } break; case RWAudio::NOISE: if (noise) { y = 1.0; } else { y = -1.0; } break; case RWAudio::WOBBLE: y = sin(aRWAudioClass->m_genPhase_l + sin(ph_wobble)); break; default: /* sine wave */ y = sin(aRWAudioClass->m_genPhase_l); break; } /* right channel */ switch (aRWAudioClass->m_genShape_r) { case RWAudio::RECT: if ((aRWAudioClass->m_genPhase_r - aRWAudioClass->m_genPhaseDif) < M_PI) { y2 = 1; } else { y2 = -1; } break; case RWAudio::SAW: y2 = ((aRWAudioClass->m_genPhase_r - aRWAudioClass->m_genPhaseDif) - M_PI) / M_PI; break; case RWAudio::TRI: if (aRWAudioClass->m_genPhase_r < M_PI) { y2 = 2 * (aRWAudioClass->m_genPhase_r - aRWAudioClass->m_genPhaseDif - M_PI / 2) / M_PI; } else { y2 = 2 * (3 * M_PI / 2 - aRWAudioClass->m_genPhase_r + aRWAudioClass->m_genPhaseDif) / M_PI; } break; case RWAudio::NOISE: if (noise) { y2 = 1.0; } else { y2 = -1.0; } break; case RWAudio::WOBBLE: y2 = sin(aRWAudioClass->m_genPhase_r - aRWAudioClass->m_genPhaseDif + sin(ph_wobble)); break; default: /* sine wave */ y2 = sin(aRWAudioClass->m_genPhase_r - aRWAudioClass->m_genPhaseDif); break; } if (aRWAudioClass->m_genGain_l == 0.0) aRWAudioClass->m_genPhase_l = 0.0; else aRWAudioClass->m_genPhase_l += (float)2.0 * M_PI * aRWAudioClass->m_genFR_l / aRWAudioClass->m_sampleRate; if (aRWAudioClass->m_genGain_r == 0.0) aRWAudioClass->m_genPhase_r = 0.0; else aRWAudioClass->m_genPhase_r += (float)2.0 * M_PI * aRWAudioClass->m_genFR_r / aRWAudioClass->m_sampleRate; if ((2.0 * M_PI) < aRWAudioClass->m_genPhase_l) aRWAudioClass->m_genPhase_l -= 2.0 * M_PI; if ((2.0 * M_PI) < aRWAudioClass->m_genPhase_r) aRWAudioClass->m_genPhase_r -= 2.0 * M_PI; ph_wobble += 30.0 / aRWAudioClass->m_sampleRate; *outBuf++ = (float)(aRWAudioClass->m_genGain_l * y); if (aRWAudioClass->m_channels_out > 1) *outBuf++ = (float)(aRWAudioClass->m_genGain_r * y2); #ifdef _DEBUG // fprintf(ddbg,"%04X %04X ",(float)(32768.f * y), (float)(32768.f * y2)); #endif } return 0; } RWAudio::RWAudio() { m_Buflen_Changed = false; m_sampleRate = 0; } RWAudio::~RWAudio() { m_AudioDriver->stopStream(); m_AudioDriver->closeStream(); } // Initialize RtAudio and start audio stream int RWAudio::InitSnd(long int oscbuflen, long int spebuflen, std::string &rtinfo, unsigned int srate) { m_OscBufferLen = oscbuflen; m_SpeBufferLen = spebuflen; m_sampleRate = srate; m_genFR_l = m_genFR_r = 0.0; m_genShape_l = m_genShape_r = SINE; m_genGain_l = m_genGain_r = 0.0; m_genPhase_l = m_genPhase_r = 0.0; m_genPhaseDif = 0.0; g_OscBufferPosition = 0; g_SpeBufferPosition = 0; g_OscBuffer_Left = (float *)malloc(m_OscBufferLen * sizeof(float)); g_OscBuffer_Right = (float *)malloc(m_OscBufferLen * sizeof(float)); g_SpeBuffer_Left = (float *)malloc(m_SpeBufferLen * sizeof(float)); g_SpeBuffer_Right = (float *)malloc(m_SpeBufferLen * sizeof(float)); RtAudio::DeviceInfo info; unsigned int devices; // Create an api map. std::map apiMap; apiMap[RtAudio::MACOSX_CORE] = "OS-X Core Audio"; apiMap[RtAudio::WINDOWS_ASIO] = "Windows ASIO"; apiMap[RtAudio::WINDOWS_DS] = "Windows DirectSound"; apiMap[RtAudio::WINDOWS_WASAPI] = "Windows WASAPI"; apiMap[RtAudio::UNIX_JACK] = "Jack Client"; apiMap[RtAudio::LINUX_ALSA] = "Linux ALSA"; apiMap[RtAudio::LINUX_PULSE] = "Linux PulseAudio"; apiMap[RtAudio::LINUX_OSS] = "Linux OSS"; apiMap[RtAudio::RTAUDIO_DUMMY] = "RtAudio Dummy"; std::vector apis; // init of RtAudio RtAudio::getCompiledApi(apis); rtinfo = "\nRtAudio Version "; rtinfo = rtinfo + RtAudio::getVersion() + "\nCompiled APIs:\n"; for (unsigned int i = 0; i < apis.size(); i++) rtinfo = rtinfo + " " + apiMap[apis[i]] + "\n"; m_AudioDriver = new RtAudio(); rtinfo = rtinfo + "Current API: " + apiMap[m_AudioDriver->getCurrentApi()] + "\n"; devices = m_AudioDriver->getDeviceCount(); if (devices < 1) return 1; for (unsigned int i = 0; i < devices; i++) { info = m_AudioDriver->getDeviceInfo(i); } // start audio streams if (RestartAudio(m_AudioDriver->getDefaultInputDevice(), m_AudioDriver->getDefaultOutputDevice())) { return 2; } return 0; } int RWAudio::RestartAudio(int recDevId, int playDevId) { // if stream is open (and running), stop it if (m_AudioDriver->isStreamOpen()) { m_AudioDriver->stopStream(); m_AudioDriver->closeStream(); } // adapt to number of channels RtAudio::DeviceInfo info = m_AudioDriver->getDeviceInfo(recDevId); if (info.inputChannels > 1 || info.duplexChannels > 1) m_channels_in = 2; else m_channels_in = 1; info = m_AudioDriver->getDeviceInfo(playDevId); if (info.outputChannels > 1 || info.duplexChannels > 1) m_channels_out = 2; else m_channels_out = 1; // configure new stream RtAudio::StreamParameters iParams; RtAudio::StreamParameters oParams; RtAudio::StreamOptions rtAOptions; unsigned int bufferFrames = 512; iParams.deviceId = recDevId; oParams.deviceId = playDevId; iParams.nChannels = m_channels_in; oParams.nChannels = m_channels_out; iParams.firstChannel = 0; oParams.firstChannel = 0; rtAOptions.flags = 0; try { m_AudioDriver->openStream(&oParams, &iParams, RTAUDIO_FLOAT32, m_sampleRate, &bufferFrames, &inout, (void *)this, &rtAOptions, &catcherr); } catch (RtAudioError &e) { // std::cerr << '\n' << e.getMessage() << '\n' << std::endl; return 1; } try { m_AudioDriver->startStream(); } catch (RtAudioError &e) { // std::cerr << '\n' << e.getMessage() << '\n' << std::endl; return 1; } return 0; } /********************************************************************/ /************* Devices enumeration *******************/ /********************************************************************/ int RWAudio::GetRWAudioDevices(RWAudioDevList *play, RWAudioDevList *record) { // Determine the number of devices available unsigned int devices = m_AudioDriver->getDeviceCount(); // Scan through devices for various capabilities RtAudio::DeviceInfo info; // if stream is open (and running), stop it if (m_AudioDriver->isStreamOpen()) { m_AudioDriver->stopStream(); m_AudioDriver->closeStream(); } play->card_info.clear(); record->card_info.clear(); play->card_pos.clear(); record->card_pos.clear(); for (unsigned int i = 0; i < devices; i++) { info = m_AudioDriver->getDeviceInfo(i); if (info.probed == true) { // std::cout << "device = " << i << "; name: " << info.name << "\n"; // add play card if ((info.outputChannels > 0) || (info.duplexChannels > 0)) { play->card_info.push_back(info); play->card_pos.push_back(i); } // add record card if ((info.inputChannels > 0) || (info.duplexChannels > 0)) { record->card_info.push_back(info); record->card_pos.push_back(i); } } } return 0; } /********************************************************************/ /************* Parameter settings *******************/ /********************************************************************/ int RWAudio::PlaySetGenerator(float f1, float f2, Waveform s1, Waveform s2, float g1, float g2) { if (2 * f1 < m_sampleRate) m_genFR_l = f1; else m_genFR_l = m_sampleRate / 2; if (2 * f2 < m_sampleRate) m_genFR_r = f2; else m_genFR_r = m_sampleRate / 2; m_genShape_l = s1; m_genShape_r = s2; if (1.0 > g1) { m_genGain_l = g1; } else { m_genGain_l = 0.99999; } if (1.0 > g2) { m_genGain_r = g2; } else { m_genGain_r = 0.99999; } return 1; } void RWAudio::SetSndDevices(unsigned int irec, unsigned int iplay, unsigned int srate) { unsigned int cardrec, cardplay; cardrec = irec; cardplay = iplay; m_sampleRate = srate; // redefine audio streams RestartAudio(cardrec, cardplay); } audmes-2025.04.05/RWAudio_IO.h000066400000000000000000000045031477556201600154600ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // RWAudio_IO - interface class between AudMeS and RtAudio // - multi-platform ////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef RWAUDIO_IO_H #define RWAUDIO_IO_H #include struct RWAudioDevList { std::vector card_info; std::vector card_pos; }; class RWAudio { public: RWAudio(); ~RWAudio(); int InitSnd(long int oscbuflen, long int spebuflen, std::string& rtinfo, unsigned int srate); void SetSndDevices(unsigned int irec, unsigned int iplay, unsigned int srate); void ChangeBufLen(long int oscbuflen, long int spebuflen) { m_OscBufferLen = oscbuflen; m_SpeBufferLen = spebuflen; m_Buflen_Changed = true; }; int GetRWAudioDevices(RWAudioDevList* play, RWAudioDevList* record); /* parameter settings */ enum Waveform { SINE, RECT, SAW, TRI, NOISE, WOBBLE }; int PlaySetGenerator(float, float, Waveform, Waveform, float, float); void PlaySetPhaseDiff(float i_fi) { m_genPhaseDif = i_fi; m_genPhase_r = m_genPhase_l; }; /* generator */ float m_genFR_l, m_genFR_r; Waveform m_genShape_l, m_genShape_r; float m_genGain_l, m_genGain_r; float m_genPhase_l, m_genPhase_r; float m_genPhaseDif; unsigned int m_sampleRate; int m_channels_in; int m_channels_out; long int m_OscBufferLen; long int m_SpeBufferLen; bool m_Buflen_Changed; protected: RtAudio* m_AudioDriver; int RestartAudio(int recDevId, int playDevId); }; #endif // RWAUDIO_IO_H audmes-2025.04.05/audmes.desktop000066400000000000000000000003151477556201600162540ustar00rootroot00000000000000[Desktop Entry] Version=1.1 Type=Application Name=AudMeS GenericName=AudMeS Comment=Audio Measurement System Icon=audmes Exec=AudMeS Actions= Categories=AudioVideo; Keywords=alsa;musicrealtime;standalone; audmes-2025.04.05/audmes.icns000066400000000000000000000464401477556201600155500ustar00rootroot00000000000000icnsM TOC it32 øt8mk@it32 øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿï…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿ…ÿ…ÿ……ÿ……ÿ……ÿÿÿÿÿÿÿÿÿ…ÿ­…ÿ½…ÿ­…ÿ½…ÿ­…ÿ½…ÿ­…ÿ½…ÿ­…ÿ½…ÿ­…ÿ½…ÿ­…ÿ½…ÿ­…ÿ½ÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿÿÿÿÿÿÿÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿ……ÿ……ÿ……ÿ……ÿ……ÿÿÿÿÿÿÿÿÿ¥…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿµ…ÿ……ÿ……ÿ……ÿ……ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿÅ…ÿ……ÿ•…ÿ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿÿ»…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿ­ÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿ•…ÿ•…ÿ……ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿï…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿí…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥ÝÿÝÿÝÿÝÿÝÿÝÿÝÿÝÿÿÿÿÿÿÿÿÿÝÿÝÿÝÿÝÿÝÿÝÿÝÿÝÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿï…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿ…ÿ……ÿ…ÿ……ÿ……ÿ……ÿí…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥…ÿ……ÿ……ÿÿ……ÿ¥ÝÿÝÿÝÿÝÿÝÿÝÿÝÿÝÿÿÿÿÿÿÿÿÿÝÿÝÿÝÿÝÿÝÿÝÿÝÿÝÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿ¥…ÿ…Åÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿµ…ÿ……ÿ…¥ÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿÅ…ÿ……ÿÿ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿõ…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­…ÿ……ÿ……ÿ…ÿ­•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿ•ÿ…•ÿ…ÿ……ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿït8mk@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿaudmes-2025.04.05/audmes.ico000066400000000000000000000015761477556201600153670ustar00rootroot00000000000000h( ½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½ÿÿ½½½½½½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½½ÿÿ€€€€€€€€€€€€€€ÿÿaudmes-2025.04.05/audmes.png000066400000000000000000000010741477556201600153720ustar00rootroot00000000000000‰PNG  IHDR€€Ã>aËsBIT|dˆóIDATxœíÝQnÛ0@A;èáÚ§§«{‰¶¬¿™ßDŒ<0½¤ âùÆï¼¶ß;þ¿þ×]pOˆ@œâ'€8Äý˜þ×ë|™àù<_jøùúsúóßÏóFW×/–:V÷7}}W_¿â '€8Ä Nqˆ@œy€‹M×AÞ`€cˆ@œâ'€8Ä}ü<Àô}ôêõýZ,“üƒ÷ñ[ÝûîØNqˆ@œâ'€¸ñ:ÀÊô}ôÕ×Oí¾û@œâ'€8Ä Î¾€Ïg_Ç'€8Ä NqˆÛ¾/`eõyøêœ¿úõæ@œâ'€8Ä nû<Àîýÿuoœ?`€cˆ@œâ'€8ÄÝ~_ÀÕß»7Ý߃yë@œâ'€8Ä nû9SÓ¹÷ÝVŸÇ¯îþêyO€8Ä Nqˆ@œâÆó»Ï³gÌ<Ç'€8Ä NqˆÏL¿wo÷ùoìŸßêîë$žqˆ@œâ'€8Ä×Vû×ÃýõSÓýýWŸc¸{Á Nqˆ@œâ'€¸ÛŸȘ}@œâ'€8Ä â/“ä}‹¬‘XIEND®B`‚audmes-2025.04.05/audmes.rc000066400000000000000000000000711477556201600152060ustar00rootroot00000000000000AudMeSIcon ICON "audmes.ico" #include "wx/msw/wx.rc" audmes-2025.04.05/audmes.xpm000066400000000000000000000007471477556201600154200ustar00rootroot00000000000000/* XPM */ static const char *audmes_xpm[] = { /* columns rows colors chars-per-pixel */ "16 16 4 1 ", " c black", ". c cyan", "X c white", "o c None", /* pixels */ "oooooooooooooooo", "o o", "o X . .X X X X o", "o . o", "o X . . X. . o", "o XX.X.X.X.X.X o", "o o", "o XX.X.X.X.X.X o", "o . ......... o", "o X X X.X.X o", "o X X .X o", "o X o", "o . . . X o", "o XX. .X. .X X o", "o o", "oooooooooooooooo" }; audmes-2025.04.05/cpp-analyze.cppcheck000066400000000000000000000016221477556201600173320ustar00rootroot00000000000000 cpp-analyze-cppcheck-build-dir unknown false true true 2 100 wxwidgets audmes-2025.04.05/dlg_audiointerface.cpp000066400000000000000000000236121477556201600177240ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////////////// // Name: dlg_audiointerface.cpp // Purpose: // Author: // Modified by: // Created: 29/03/2007 11:48:34 // RCS-ID: // Copyright: // Licence: ///////////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2007-2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" #ifndef WX_PRECOMP #include "wx/wx.h" #endif ////@begin includes ////@end includes #include "dlg_audiointerface.h" #include "event_ids.h" ////@begin XPM images ////@end XPM images /*! * AudioInterfaceDialog type definition */ wxIMPLEMENT_DYNAMIC_CLASS(AudioInterfaceDialog, wxDialog); /*! * AudioInterfaceDialog event table definition */ wxBEGIN_EVENT_TABLE(AudioInterfaceDialog, wxDialog) EVT_CHOICE(ID_OUTDEV_CHO, AudioInterfaceDialog::OnChoiceChanged) EVT_CHOICE(ID_INDEV_CHO, AudioInterfaceDialog::OnChoiceChanged) wxEND_EVENT_TABLE() /*! * AudioInterfaceDialog constructors */ AudioInterfaceDialog::AudioInterfaceDialog() {} AudioInterfaceDialog::AudioInterfaceDialog(wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style) { Create(parent, id, caption, pos, size, style); } /*! * AudioInterfaceDialog creator */ bool AudioInterfaceDialog::Create(wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style) { ////@begin AudioInterfaceDialog creation SetExtraStyle(GetExtraStyle() | wxWS_EX_BLOCK_EVENTS); wxDialog::Create(parent, id, caption, pos, size, style); CreateControls(); if (GetSizer()) { GetSizer()->SetSizeHints(this); } Centre(); ////@end AudioInterfaceDialog creation return true; } /*! * Control creation for AudioInterfaceDialog */ void AudioInterfaceDialog::CreateControls() { ////@begin AudioInterfaceDialog content construction AudioInterfaceDialog* itemDialog1 = this; wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL); itemDialog1->SetSizer(itemBoxSizer2); wxStaticBox* itemFreqStaticBox = new wxStaticBox(itemDialog1, wxID_ANY, _("Available Frequencies [Hz]")); wxStaticBoxSizer* itemFreqStaticBoxSizer = new wxStaticBoxSizer(itemFreqStaticBox, wxHORIZONTAL); itemBoxSizer2->Add(itemFreqStaticBoxSizer, 1, wxEXPAND | wxALL, 5); wxString* itemFreqChoiceStrings = NULL; wxChoice* itemFreqChoice = new wxChoice(itemDialog1, ID_FREQ_CHO, wxDefaultPosition, wxDefaultSize, 0, itemFreqChoiceStrings, 0); itemFreqStaticBoxSizer->Add(itemFreqChoice, 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxStaticBox* itemStaticBoxSizer3Static = new wxStaticBox(itemDialog1, wxID_ANY, _("Output Audio Device")); wxStaticBoxSizer* itemStaticBoxSizer3 = new wxStaticBoxSizer(itemStaticBoxSizer3Static, wxHORIZONTAL); itemBoxSizer2->Add(itemStaticBoxSizer3, 1, wxEXPAND | wxALL, 5); wxString* itemChoice4Strings = NULL; wxChoice* itemChoice4 = new wxChoice(itemDialog1, ID_OUTDEV_CHO, wxDefaultPosition, wxDefaultSize, 0, itemChoice4Strings, 0); itemStaticBoxSizer3->Add(itemChoice4, 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxStaticBox* itemStaticBoxSizer5Static = new wxStaticBox(itemDialog1, wxID_ANY, _("Input Audio Device")); wxStaticBoxSizer* itemStaticBoxSizer5 = new wxStaticBoxSizer(itemStaticBoxSizer5Static, wxHORIZONTAL); itemBoxSizer2->Add(itemStaticBoxSizer5, 0, wxEXPAND | wxALL, 5); wxString* itemChoice6Strings = NULL; wxChoice* itemChoice6 = new wxChoice(itemDialog1, ID_INDEV_CHO, wxDefaultPosition, wxDefaultSize, 0, itemChoice6Strings, 0); itemStaticBoxSizer5->Add(itemChoice6, 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxBoxSizer* itemBoxSizer7 = new wxBoxSizer(wxHORIZONTAL); itemBoxSizer2->Add(itemBoxSizer7, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); wxButton* itemButton8 = new wxButton(itemDialog1, wxID_OK, _("&OK"), wxDefaultPosition, wxDefaultSize, 0); itemBoxSizer7->Add(itemButton8, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); itemBoxSizer7->Add(5, 5, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxButton* itemButton10 = new wxButton(itemDialog1, wxID_CANCEL, _("&Cancel"), wxDefaultPosition, wxDefaultSize, 0); itemBoxSizer7->Add(itemButton10, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); ////@end AudioInterfaceDialog content construction } /*! * Should we show tooltips? */ bool AudioInterfaceDialog::ShowToolTips() { return true; } /*! * Get bitmap resources */ wxBitmap AudioInterfaceDialog::GetBitmapResource(const wxString& name) { // Bitmap retrieval ////@begin AudioInterfaceDialog bitmap retrieval wxUnusedVar(name); return wxNullBitmap; ////@end AudioInterfaceDialog bitmap retrieval } /*! * Get icon resources */ wxIcon AudioInterfaceDialog::GetIconResource(const wxString& name) { // Icon retrieval ////@begin AudioInterfaceDialog icon retrieval wxUnusedVar(name); return wxNullIcon; ////@end AudioInterfaceDialog icon retrieval } void AudioInterfaceDialog::SetDevices(RWAudioDevList devreclist, RWAudioDevList devpllist, AIStreamSettings streamSettings) { unsigned int pldev = 0, recdev = 0; unsigned int cfreq = 0; m_DevRecList = devreclist; m_DevPlayList = devpllist; m_freq = streamSettings.freq; wxChoice* p_cho = (wxChoice*)FindWindow(ID_OUTDEV_CHO); if (!p_cho) { return; } p_cho->Clear(); int curSelection = 0; for (unsigned int i = 0; i < devpllist.card_info.size(); i++) { wxString newstr(devpllist.card_info[i].name.c_str(), wxConvUTF8); p_cho->Append(newstr); if (devpllist.card_pos[i] == streamSettings.playDev) { curSelection = i; } } p_cho->SetSelection(curSelection); p_cho = (wxChoice*)FindWindow(ID_INDEV_CHO); if (!p_cho) { return; } p_cho->Clear(); curSelection = 0; for (unsigned int i = 0; i < devreclist.card_info.size(); i++) { wxString newstr(devreclist.card_info[i].name.c_str(), wxConvUTF8); p_cho->Append(newstr); if (devreclist.card_pos[i] == streamSettings.recordDev) { curSelection = i; } } p_cho->SetSelection(curSelection); p_cho = (wxChoice*)FindWindow(ID_FREQ_CHO); if (!p_cho) { return; } p_cho->Clear(); if (0 < devreclist.card_info.size() && 0 < devpllist.card_info.size()) { // compute the new list - find the same values in DevRecList and DevPlayList for (unsigned int i = 0; i < m_DevPlayList.card_info[pldev].sampleRates.size(); i++) { for (unsigned int j = 0; j < m_DevRecList.card_info[recdev].sampleRates.size(); j++) { unsigned int srateplay = m_DevPlayList.card_info[pldev].sampleRates[i]; unsigned int sraterec = m_DevRecList.card_info[recdev].sampleRates[j]; if (srateplay == sraterec) { if (srateplay == m_freq) cfreq = p_cho->GetCount(); p_cho->Append(wxString::Format(wxT("%u "), srateplay)); } } } } if (p_cho->GetCount() > 0) { p_cho->SetSelection(cfreq); } if (GetSizer()) { GetSizer()->SetSizeHints(this); } } void AudioInterfaceDialog::GetSelectedDevs(unsigned int* recdev, unsigned int* playdev, unsigned int* newfreq) { wxString strfreq; int seldev; wxChoice* p_cho = (wxChoice*)FindWindow(ID_OUTDEV_CHO); if (!p_cho) { return; } if (0 >= m_DevPlayList.card_info.size()) return; seldev = p_cho->GetSelection(); *playdev = m_DevPlayList.card_pos[seldev]; p_cho = (wxChoice*)FindWindow(ID_INDEV_CHO); if (!p_cho) { return; } if (0 >= m_DevRecList.card_info.size()) return; seldev = p_cho->GetSelection(); *recdev = m_DevRecList.card_pos[seldev]; p_cho = (wxChoice*)FindWindow(ID_FREQ_CHO); if (!p_cho) { return; } strfreq = p_cho->GetString(p_cho->GetSelection()); *newfreq = (unsigned int)wxAtoi(strfreq); } // fill the frequency table void AudioInterfaceDialog::OnChoiceChanged(wxCommandEvent& WXUNUSED(event)) { unsigned int pldev, recdev; unsigned int cfreq = 0; wxChoice* p_cho = (wxChoice*)FindWindow(ID_OUTDEV_CHO); if (!p_cho) { return; } pldev = p_cho->GetSelection(); p_cho = (wxChoice*)FindWindow(ID_INDEV_CHO); if (!p_cho) { return; } recdev = p_cho->GetSelection(); // clean the frequency list p_cho = (wxChoice*)FindWindow(ID_FREQ_CHO); if (!p_cho) { return; } p_cho->Clear(); if (0 < m_DevRecList.card_info.size() && 0 < m_DevPlayList.card_info.size()) { // compute the new list - find the same values in DevRecList and DevPlayList for (unsigned int i = 0; i < m_DevPlayList.card_info[pldev].sampleRates.size(); i++) { for (unsigned int j = 0; j < m_DevRecList.card_info[recdev].sampleRates.size(); j++) { unsigned int srateplay = m_DevPlayList.card_info[pldev].sampleRates[i]; unsigned int sraterec = m_DevRecList.card_info[recdev].sampleRates[j]; if (srateplay == sraterec) { if (srateplay == m_freq) cfreq = p_cho->GetCount(); p_cho->Append(wxString::Format(wxT("%u "), srateplay)); } } } } if (p_cho->GetCount() > 0) { p_cho->SetSelection(cfreq); } } audmes-2025.04.05/dlg_audiointerface.h000066400000000000000000000077041477556201600173750ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////////////// // Name: dlg_audiointerface.h // Purpose: // Author: // Modified by: // Created: 29/03/2007 11:48:34 // RCS-ID: // Copyright: // Licence: ///////////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2007-2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef _DLG_AUDIOINTERFACE_H_ #define _DLG_AUDIOINTERFACE_H_ /*! * Includes */ #include "RWAudio_IO.h" ////@begin includes ////@end includes /*! * Forward declarations */ ////@begin forward declarations ////@end forward declarations /*! * Control identifiers */ ////@begin control identifiers #define SYMBOL_AUDIOINTERFACEDIALOG_STYLE wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU | wxCLOSE_BOX #define SYMBOL_AUDIOINTERFACEDIALOG_TITLE _("Audio Interface Configuration") #define SYMBOL_AUDIOINTERFACEDIALOG_IDNAME 32000 #define SYMBOL_AUDIOINTERFACEDIALOG_SIZE wxSize(400, 300) #define SYMBOL_AUDIOINTERFACEDIALOG_POSITION wxDefaultPosition ////@end control identifiers /*! * Compatibility */ #ifndef wxCLOSE_BOX #define wxCLOSE_BOX 0x1000 #endif /*! * AudioInterfaceDialog class declaration */ struct AIStreamSettings { unsigned int playDev; unsigned int recordDev; unsigned int freq; }; class AudioInterfaceDialog : public wxDialog { wxDECLARE_DYNAMIC_CLASS(AudioInterfaceDialog); wxDECLARE_EVENT_TABLE(); RWAudioDevList m_DevRecList; RWAudioDevList m_DevPlayList; AIStreamSettings m_StreamSettings; unsigned int m_freq; public: /// Constructors AudioInterfaceDialog(); AudioInterfaceDialog(wxWindow* parent, wxWindowID id = SYMBOL_AUDIOINTERFACEDIALOG_IDNAME, const wxString& caption = SYMBOL_AUDIOINTERFACEDIALOG_TITLE, const wxPoint& pos = SYMBOL_AUDIOINTERFACEDIALOG_POSITION, const wxSize& size = SYMBOL_AUDIOINTERFACEDIALOG_SIZE, long style = SYMBOL_AUDIOINTERFACEDIALOG_STYLE); /// Creation bool Create(wxWindow* parent, wxWindowID id = SYMBOL_AUDIOINTERFACEDIALOG_IDNAME, const wxString& caption = SYMBOL_AUDIOINTERFACEDIALOG_TITLE, const wxPoint& pos = SYMBOL_AUDIOINTERFACEDIALOG_POSITION, const wxSize& size = SYMBOL_AUDIOINTERFACEDIALOG_SIZE, long style = SYMBOL_AUDIOINTERFACEDIALOG_STYLE); /// Creates the controls and sizers void CreateControls(); ////@begin AudioInterfaceDialog event handler declarations ////@end AudioInterfaceDialog event handler declarations ////@begin AudioInterfaceDialog member function declarations /// Retrieves bitmap resources wxBitmap GetBitmapResource(const wxString& name); /// Retrieves icon resources wxIcon GetIconResource(const wxString& name); ////@end AudioInterfaceDialog member function declarations /// Should we show tooltips? static bool ShowToolTips(); ////@begin AudioInterfaceDialog member variables ////@end AudioInterfaceDialog member variables void SetDevices(RWAudioDevList devreclist, RWAudioDevList devpllist, AIStreamSettings streamSettings); void GetSelectedDevs(unsigned int* recdev, unsigned int* playdev, unsigned int* newfreq); void OnChoiceChanged(wxCommandEvent& event); }; #endif // _DLG_AUDIOINTERFACE_H_ audmes-2025.04.05/event_ids.h000066400000000000000000000033161477556201600155400ustar00rootroot00000000000000// -*- C++ -*- // event ID list /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define ID_SPANSTART 10000 #define ID_GENSTART 10001 #define ID_OSCSTART 10002 #define ID_TIMERID 10003 #define ID_GENLENB 10004 #define ID_GENRENB 10005 #define ID_GENLFREQ 10006 #define ID_GENRFREQ 10007 #define ID_GENLAMP 10008 #define ID_GENRAMP 10009 #define ID_OSCLTRIG 10010 #define ID_OSCRTRIG 10011 #define ID_FRMSTART 10012 #define ID_SNDCARD 10013 #define ID_GENSYNC 10014 #define ID_GENPHASE 10015 #define ID_OUTDEV_CHO 10016 #define ID_INDEV_CHO 10017 #define ID_TXT_FREQ_L 10018 #define ID_TXT_FREQ_R 10019 #define ID_GENSHP_L 10020 #define ID_GENSHP_R 10021 #define ID_AUTOCAL 10022 #define ID_OSCXSCALE 10023 #define ID_FFTLENGTH 10024 #define ID_FREQ_CHO 10025 #define ID_LOAD_FRM 10026 #define ID_SAVE_FRM 10027 #define ID_FFTWINDOW 10028 #define ID_FFTAVG 10029 #define ID_FFTREF 10030 #define ID_FFTDBDIV 10031 #define ID_LOAD_SPE 10032 #define ID_SAVE_SPE 10033 #define ID_SAVE_OSC 10034 #define ID_SINC 10035 audmes-2025.04.05/fourier.c000066400000000000000000000226051477556201600152300ustar00rootroot00000000000000/*============================================================================ fourier.c (originally fourier(f)(d).c and fftmisc.c) - Don Cross The file FFT.ZIP contains C source code for performing Discrete Fast Fourier Transforms (DFFTs) and inverse DFFTs. Please see fouier.txt which was the readme.txt that Don Cross provided. This source code is public domain. Use at your own risk. For more information, point your web browser at: (dead link) http://www.intersrv.com/~dcross/fft.html Helper routines for Fast Fourier Transform implementation. Contains common code for fft_float() and fft_double(). See also: ..\include\fourier.h Revision history: 1998 September 19 [Don Cross] Improved the efficiency of IsPowerOfTwo(). Updated coding standards. 2003 [John Labenski] Merged fourierd.c (double), fourierf.c (float), and fftmisc.c into fourier.c for simplicity. ============================================================================*/ #include #include #include #include "fourier.h" #define BITS_PER_WORD (sizeof(unsigned) * 8) #define DDC_PI (3.14159265358979323846) /*--- start of file fftmisc.c---*/ // #define IsPowerOfTwo(x) (((x)<2) || ((x)&((x)-1)) ? false : true) // returns 1 if the numver is a power of two else 0 int IsPowerOfTwo ( unsigned x ) { if ( x < 2 ) return 0; if ( x & (x-1) ) // Thanks to 'byang' for this cute trick! return 0; return 1; } // returns the number of bits needed unsigned NumberOfBitsNeeded ( unsigned PowerOfTwo ) { unsigned i; for ( i=0; ; i++ ) { if ( PowerOfTwo & (1 << i) ) return i; } return 0; } unsigned ReverseBits ( unsigned index, unsigned NumBits ) { unsigned i, rev; for ( i=rev=0; i < NumBits; i++ ) { rev = (rev << 1) | (index & 1); index >>= 1; } return rev; } double Index_to_frequency ( unsigned NumSamples, unsigned Index ) { if ( Index >= NumSamples ) return 0.0; else if ( Index <= NumSamples/2 ) return (double)Index / (double)NumSamples; return -(double)(NumSamples-Index) / (double)NumSamples; } /*--- end of file fftmisc.c---*/ /*============================================================================ fourierd.c - Don Cross http://www.intersrv.com/~dcross/fft.html Contains definitions for doing Fourier transforms and inverse Fourier transforms. This module performs operations on arrays of 'double'. Revision history: 1998 September 19 [Don Cross] Updated coding standards. Improved efficiency of trig calculations. ============================================================================*/ #define CHECKPOINTERDOUBLE(p) if (CheckPointerDouble(p,#p) == 0) return 0; static int CheckPointerDouble ( void *p, const char *name ) { if ( p == NULL ) { fprintf ( stderr, "Error in fft_double(): %s == NULL\n", name ); return 0; } return 1; } int fft_double ( unsigned NumSamples, int InverseTransform, double *RealIn, double *ImagIn, double *RealOut, double *ImagOut ) { unsigned NumBits; /* Number of bits needed to store indices */ unsigned i, j, k, n; unsigned BlockSize, BlockEnd; double angle_numerator = -2.0 * DDC_PI; // this is - to match matlab double tr, ti; /* temp real, temp imaginary */ if ( !IsPowerOfTwo(NumSamples) || (NumSamples < 2) ) { fprintf ( stderr, "Error in fft(): NumSamples=%u is not power of two\n", NumSamples ); return 0; } if ( InverseTransform ) angle_numerator = -angle_numerator; CHECKPOINTERDOUBLE ( RealIn ); CHECKPOINTERDOUBLE ( RealOut ); CHECKPOINTERDOUBLE ( ImagOut ); NumBits = NumberOfBitsNeeded ( NumSamples ); /* ** Do simultaneous data copy and bit-reversal ordering into outputs... */ for ( i=0; i < NumSamples; i++ ) { j = ReverseBits ( i, NumBits ); RealOut[j] = RealIn[i]; ImagOut[j] = (ImagIn == NULL) ? 0.0 : ImagIn[i]; } /* ** Do the FFT itself... */ BlockEnd = 1; for ( BlockSize = 2; BlockSize <= NumSamples; BlockSize <<= 1 ) { double delta_angle = angle_numerator / (double)BlockSize; double sm2 = sin ( -2 * delta_angle ); double sm1 = sin ( -delta_angle ); double cm2 = cos ( -2 * delta_angle ); double cm1 = cos ( -delta_angle ); double w = 2 * cm1; /* double ar[3], ai[3]; replaced array with fixed vals below - labenski */ double ar0, ar1, ar2, ai0, ai1, ai2; for ( i=0; i < NumSamples; i += BlockSize ) { ar2 = cm2; ar1 = cm1; ai2 = sm2; ai1 = sm1; for ( j=i, n=0; n < BlockEnd; j++, n++ ) { ar0 = w*ar1 - ar2; ar2 = ar1; ar1 = ar0; ai0 = w*ai1 - ai2; ai2 = ai1; ai1 = ai0; k = j + BlockEnd; tr = ar0*RealOut[k] - ai0*ImagOut[k]; ti = ar0*ImagOut[k] + ai0*RealOut[k]; RealOut[k] = RealOut[j] - tr; ImagOut[k] = ImagOut[j] - ti; RealOut[j] += tr; ImagOut[j] += ti; } } BlockEnd = BlockSize; } /* ** Need to normalize if inverse transform... */ if ( InverseTransform ) { double denom = (double)NumSamples; for ( i=0; i < NumSamples; i++ ) { RealOut[i] /= denom; ImagOut[i] /= denom; } } return 1; } /*--- end of file fourierd.c ---*/ /*============================================================================ fourierf.c - Don Cross http://www.intersrv.com/~dcross/fft.html Contains definitions for doing Fourier transforms and inverse Fourier transforms. This module performs operations on arrays of 'float'. Revision history: 1998 September 19 [Don Cross] Updated coding standards. Improved efficiency of trig calculations. ============================================================================*/ #define CHECKPOINTERFLOAT(p) if (CheckPointerFloat(p,#p) == 0) return 0; static int CheckPointerFloat ( void *p, const char *name ) { if ( p == NULL ) { fprintf ( stderr, "Error in fft_float(): %s == NULL\n", name ); return 0; } return 1; } int fft_float ( unsigned NumSamples, int InverseTransform, float *RealIn, float *ImagIn, float *RealOut, float *ImagOut ) { unsigned NumBits; /* Number of bits needed to store indices */ unsigned i, j, k, n; unsigned BlockSize, BlockEnd; double angle_numerator = -2.0 * DDC_PI; // this is - to match matlab double tr, ti; /* temp real, temp imaginary */ if ( !IsPowerOfTwo(NumSamples) || (NumSamples < 2) ) { fprintf ( stderr, "Error in fft(): NumSamples=%u is not power of two\n", NumSamples ); return 0; } if ( InverseTransform ) angle_numerator = -angle_numerator; CHECKPOINTERFLOAT ( RealIn ); CHECKPOINTERFLOAT ( RealOut ); CHECKPOINTERFLOAT ( ImagOut ); NumBits = NumberOfBitsNeeded ( NumSamples ); /* ** Do simultaneous data copy and bit-reversal ordering into outputs... */ for ( i=0; i < NumSamples; i++ ) { j = ReverseBits ( i, NumBits ); RealOut[j] = RealIn[i]; ImagOut[j] = (ImagIn == NULL) ? (float)0.0 : ImagIn[i]; } /* ** Do the FFT itself... */ BlockEnd = 1; for ( BlockSize = 2; BlockSize <= NumSamples; BlockSize <<= 1 ) { double delta_angle = angle_numerator / (double)BlockSize; double sm2 = sin ( -2 * delta_angle ); double sm1 = sin ( -delta_angle ); double cm2 = cos ( -2 * delta_angle ); double cm1 = cos ( -delta_angle ); double w = 2 * cm1; /* double ar[3], ai[3]; replaced array with fixed vals below - labenski */ double ar0, ar1, ar2, ai0, ai1, ai2; /* double temp; */ for ( i=0; i < NumSamples; i += BlockSize ) { ar2 = cm2; ar1 = cm1; ai2 = sm2; ai1 = sm1; for ( j=i, n=0; n < BlockEnd; j++, n++ ) { ar0 = w*ar1 - ar2; ar2 = ar1; ar1 = ar0; ai0 = w*ai1 - ai2; ai2 = ai1; ai1 = ai0; k = j + BlockEnd; tr = ar0*RealOut[k] - ai0*ImagOut[k]; ti = ar0*ImagOut[k] + ai0*RealOut[k]; RealOut[k] = (float)(RealOut[j] - tr); ImagOut[k] = (float)(ImagOut[j] - ti); RealOut[j] += (float)tr; ImagOut[j] += (float)ti; } } BlockEnd = BlockSize; } /* ** Need to normalize if inverse transform... */ if ( InverseTransform ) { float denom = (float)NumSamples; for ( i=0; i < NumSamples; i++ ) { RealOut[i] /= denom; ImagOut[i] /= denom; } } return 1; } /*--- end of file fourierf.c ---*/ audmes-2025.04.05/fourier.h000066400000000000000000000060331477556201600152320ustar00rootroot00000000000000/*============================================================================ fourier.h - Don Cross The file FFT.ZIP contains C source code for performing Discrete Fast Fourier Transforms (DFFTs) and inverse DFFTs. Please see fouier.txt which was the readme.txt that Don Cross provided. This source code is public domain. Use at your own risk. For more information, point your web browser at: (dead link) http://www.intersrv.com/~dcross/fft.html Contains definitions for doing Fourier transforms and inverse Fourier transforms. 2003 [John Labenski] Merged fourierd.c (double), fourierf.c (float), and fftmisc.c into fourier.c for simplicity. ============================================================================*/ #ifndef _FOURIER_FFT_H_ #define _FOURIER_FFT_H_ #ifdef __cplusplus extern "C" { #endif /* ** fft() computes the Fourier transform or inverse transform ** of the complex inputs to produce the complex outputs. ** The number of samples must be a power of two to do the ** recursive decomposition of the FFT algorithm. ** See Chapter 12 of "Numerical Recipes in FORTRAN" by ** Press, Teukolsky, Vetterling, and Flannery, ** Cambridge University Press. ** ** returns 1 on success or 0 on failure ** ** Notes: If you pass ImaginaryIn = NULL, this function will "pretend" ** that it is an array of all zeroes. This is convenient for ** transforming digital samples of real number data without ** wasting memory. */ int fft_double ( unsigned NumSamples, /* must be a power of 2 */ int InverseTransform, /* 0=forward FFT, 1=inverse FFT */ double *RealIn, /* array of input's real samples */ double *ImaginaryIn, /* array of input's imag samples */ double *RealOut, /* array of output's reals */ double *ImaginaryOut ); /* array of output's imaginaries */ int fft_float ( unsigned NumSamples, /* must be a power of 2 */ int InverseTransform, /* 0=forward FFT, 1=inverse FFT */ float *RealIn, /* array of input's real samples */ float *ImaginaryIn, /* array of input's imag samples */ float *RealOut, /* array of output's reals */ float *ImaginaryOut ); /* array of output's imaginaries */ /* IsPowerOfTwo returns 1 if x is a power of two or 0 if not */ int IsPowerOfTwo ( unsigned x ); /* NumberOfBitsNeeded returns the number of bits to make the number, 2^n */ unsigned NumberOfBitsNeeded ( unsigned PowerOfTwo ); unsigned ReverseBits ( unsigned index, unsigned NumBits ); /* ** The following function returns an "abstract frequency" of a ** given index into a buffer with a given number of frequency samples. ** Multiply return value by sampling rate to get frequency expressed in Hz. */ double Index_to_frequency ( unsigned NumSamples, unsigned Index ); #ifdef __cplusplus } #endif #endif // _FOURIER_FFT_H_ /*--- end of file fourier.h ---*/ audmes-2025.04.05/net.sourceforge.audmes.audmes.appdata.xml000066400000000000000000000050041477556201600234000ustar00rootroot00000000000000 net.sourceforge.audmes.audmes CC0-1.0 GPL-2.0 audmes Audio measurement system

Analyze audio systems using a sound card. Features include:

  • Tone generator
  • Oscilloscope
  • Spectrum analyzer
  • Frequency response plot
net.sourceforge.audmes.audmes.desktop https://audmes.sourceforge.net/ UserDocs
  • Rearrange oscilloscope view
  • Normalize vertical settings to +- 1V and adapt settings
  • Remove DC from Spectrum
  • Fix clipping region in graph
  • Refactor interface between AudMeS and CtrlOscope: Pass time or frequency data (y axis) along with magnitude (x axis).
  • Rewrite graph code taking advantage of the y axis data.
  • Add save function to oscilloscope and spectrum.
  • Use time instead of samples in oscilloscope.
AudMeS Team https://sourceforge.net/projects/audmes/ https://a.fsdn.com/con/app/proj/audmes/screenshots/Audmes-spectrum.png/max/max/1
audmes-2025.04.05/rtaudio/000077500000000000000000000000001477556201600150535ustar00rootroot00000000000000audmes-2025.04.05/rtaudio/RtAudio.cpp000066400000000000000000013603451477556201600171420ustar00rootroot00000000000000/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. RtAudio provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA, Jack, and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. RtAudio GitHub site: https://github.com/thestk/rtaudio RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes Copyright (c) 2001-2021 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /************************************************************************/ // RtAudio: Version 5.2.0 #include "RtAudio.h" #include #include #include #include #include #include // Static variable definitions. const unsigned int RtApi::MAX_SAMPLE_RATES = 14; const unsigned int RtApi::SAMPLE_RATES[] = { 4000, 5512, 8000, 9600, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; #if defined(_WIN32) || defined(__CYGWIN__) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) #include "tchar.h" template inline std::string convertCharPointerToStdString(const T *text); template<> inline std::string convertCharPointerToStdString(const char *text) { return std::string(text); } template<> inline std::string convertCharPointerToStdString(const wchar_t *text) { int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); std::string s( length-1, '\0' ); WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL); return s; } #elif defined(__unix__) || defined(__APPLE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) #endif // *************************************************** // // // RtAudio definitions. // // *************************************************** // std::string RtAudio :: getVersion( void ) { return RTAUDIO_VERSION; } // Define API names and display names. // Must be in same order as API enum. extern "C" { const char* rtaudio_api_names[][2] = { { "unspecified" , "Unknown" }, { "alsa" , "ALSA" }, { "pulse" , "Pulse" }, { "oss" , "OpenSoundSystem" }, { "jack" , "Jack" }, { "core" , "CoreAudio" }, { "wasapi" , "WASAPI" }, { "asio" , "ASIO" }, { "ds" , "DirectSound" }, { "dummy" , "Dummy" }, }; const unsigned int rtaudio_num_api_names = sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); // The order here will control the order of RtAudio's API search in // the constructor. extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { #if defined(__UNIX_JACK__) RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) RtAudio::LINUX_PULSE, #endif #if defined(__LINUX_ALSA__) RtAudio::LINUX_ALSA, #endif #if defined(__LINUX_OSS__) RtAudio::LINUX_OSS, #endif #if defined(__WINDOWS_ASIO__) RtAudio::WINDOWS_ASIO, #endif #if defined(__WINDOWS_WASAPI__) RtAudio::WINDOWS_WASAPI, #endif #if defined(__WINDOWS_DS__) RtAudio::WINDOWS_DS, #endif #if defined(__MACOSX_CORE__) RtAudio::MACOSX_CORE, #endif #if defined(__RTAUDIO_DUMMY__) RtAudio::RTAUDIO_DUMMY, #endif RtAudio::UNSPECIFIED, }; extern "C" const unsigned int rtaudio_num_compiled_apis = sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; } // This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. // If the build breaks here, check that they match. template class StaticAssert { private: StaticAssert() {} }; template<> class StaticAssert{ public: StaticAssert() {} }; class StaticAssertions { StaticAssertions() { StaticAssert(); }}; void RtAudio :: getCompiledApi( std::vector &apis ) { apis = std::vector(rtaudio_compiled_apis, rtaudio_compiled_apis + rtaudio_num_compiled_apis); } std::string RtAudio :: getApiName( RtAudio::Api api ) { if (api < 0 || api >= RtAudio::NUM_APIS) return ""; return rtaudio_api_names[api][0]; } std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) { if (api < 0 || api >= RtAudio::NUM_APIS) return "Unknown"; return rtaudio_api_names[api][1]; } RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) { unsigned int i=0; for (i = 0; i < rtaudio_num_compiled_apis; ++i) if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][0]) return rtaudio_compiled_apis[i]; return RtAudio::UNSPECIFIED; } void RtAudio :: openRtApi( RtAudio::Api api ) { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new RtApiJack(); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new RtApiAlsa(); #endif #if defined(__LINUX_PULSE__) if ( api == LINUX_PULSE ) rtapi_ = new RtApiPulse(); #endif #if defined(__LINUX_OSS__) if ( api == LINUX_OSS ) rtapi_ = new RtApiOss(); #endif #if defined(__WINDOWS_ASIO__) if ( api == WINDOWS_ASIO ) rtapi_ = new RtApiAsio(); #endif #if defined(__WINDOWS_WASAPI__) if ( api == WINDOWS_WASAPI ) rtapi_ = new RtApiWasapi(); #endif #if defined(__WINDOWS_DS__) if ( api == WINDOWS_DS ) rtapi_ = new RtApiDs(); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new RtApiCore(); #endif #if defined(__RTAUDIO_DUMMY__) if ( api == RTAUDIO_DUMMY ) rtapi_ = new RtApiDummy(); #endif } RtAudio :: RtAudio( RtAudio::Api api ) { rtapi_ = 0; if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openRtApi( api ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a debug // warning and continue as if no API was specified. std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one device or we reach the end of the list. std::vector< RtAudio::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetDeviceCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTAUDIO_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll throw an error. std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } RtAudio :: ~RtAudio() { if ( rtapi_ ) delete rtapi_; } void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, userData, options, errorCallback ); } // *************************************************** // // // Public RtApi definitions (see end of file for // private or protected utility functions). // // *************************************************** // RtApi :: RtApi() { stream_.state = STREAM_CLOSED; stream_.mode = UNINITIALIZED; stream_.apiHandle = 0; stream_.userBuffer[0] = 0; stream_.userBuffer[1] = 0; MUTEX_INITIALIZE( &stream_.mutex ); showWarnings_ = true; firstErrorOccurred_ = false; } RtApi :: ~RtApi() { MUTEX_DESTROY( &stream_.mutex ); } void RtApi :: openStream( RtAudio::StreamParameters *oParams, RtAudio::StreamParameters *iParams, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; error( RtAudioError::INVALID_USE ); return; } // Clear stream information potentially left from a previously open stream. clearStreamInfo(); if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; error( RtAudioError::INVALID_USE ); return; } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; error( RtAudioError::INVALID_USE ); return; } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; error( RtAudioError::INVALID_USE ); return; } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; error( RtAudioError::INVALID_USE ); return; } unsigned int nDevices = getDeviceCount(); unsigned int oChannels = 0; if ( oParams ) { oChannels = oParams->nChannels; if ( oParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: output device parameter value is invalid."; error( RtAudioError::INVALID_USE ); return; } } unsigned int iChannels = 0; if ( iParams ) { iChannels = iParams->nChannels; if ( iParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: input device parameter value is invalid."; error( RtAudioError::INVALID_USE ); return; } } bool result; if ( oChannels > 0 ) { result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { error( RtAudioError::SYSTEM_ERROR ); return; } } if ( iChannels > 0 ) { result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { if ( oChannels > 0 ) closeStream(); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; stream_.callbackInfo.errorCallback = (void *) errorCallback; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; } unsigned int RtApi :: getDefaultInputDevice( void ) { // Should be reimplemented in subclasses if necessary. unsigned int nDevices = getDeviceCount(); for ( unsigned int i = 0; i < nDevices; i++ ) { if ( getDeviceInfo( i ).isDefaultInput ) { return i; } } return 0; } unsigned int RtApi :: getDefaultOutputDevice( void ) { // Should be reimplemented in subclasses if necessary. unsigned int nDevices = getDeviceCount(); for ( unsigned int i = 0; i < nDevices; i++ ) { if ( getDeviceInfo( i ).isDefaultOutput ) { return i; } } return 0; } void RtApi :: closeStream( void ) { // MUST be implemented in subclasses! return; } bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) { // MUST be implemented in subclasses! return FAILURE; } void RtApi :: tickStreamTime( void ) { // Subclasses that do not provide their own implementation of // getStreamTime should call this function once per buffer I/O to // provide basic stream time support. stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif } long RtApi :: getStreamLatency( void ) { verifyStream(); long totalLatency = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) totalLatency = stream_.latency[0]; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) totalLatency += stream_.latency[1]; return totalLatency; } double RtApi :: getStreamTime( void ) { verifyStream(); #if defined( HAVE_GETTIMEOFDAY ) // Return a very accurate estimate of the stream time by // adding in the elapsed time since the last tick. struct timeval then; struct timeval now; if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) return stream_.streamTime; gettimeofday( &now, NULL ); then = stream_.lastTickTimestamp; return stream_.streamTime + ((now.tv_sec + 0.000001 * now.tv_usec) - (then.tv_sec + 0.000001 * then.tv_usec)); #else return stream_.streamTime; #endif } void RtApi :: setStreamTime( double time ) { verifyStream(); if ( time >= 0.0 ) stream_.streamTime = time; #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif } unsigned int RtApi :: getStreamSampleRate( void ) { verifyStream(); return stream_.sampleRate; } // *************************************************** // // // OS/API-specific methods. // // *************************************************** // #if defined(__MACOSX_CORE__) #include // The OS X CoreAudio API is designed to use a separate callback // procedure for each of its audio devices. A single RtAudio duplex // stream using two different devices is supported here, though it // cannot be guaranteed to always behave correctly because we cannot // synchronize these two callbacks. // // A property listener is installed for over/underrun information. // However, no functionality is currently provided to allow property // listeners to trigger user handlers because it is unclear what could // be done if a critical stream parameter (buffer size, sample rate, // device disconnect) notification arrived. The listeners entail // quite a bit of extra code and most likely, a user program wouldn't // be prepared for the result anyway. However, we do provide a flag // to the client callback function to inform of an over/underrun. // A structure to hold various information related to the CoreAudio API // implementation. struct CoreHandle { AudioDeviceID id[2]; // device ids #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceIOProcID procId[2]; #endif UInt32 iStream[2]; // device stream index (or first if using multiple) UInt32 nStreams[2]; // number of streams to use bool xrun[2]; char *deviceBuffer; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. CoreHandle() :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiCore:: RtApiCore() { #if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) // This is a largely undocumented but absolutely necessary // requirement starting with OS-X 10.6. If not called, queries and // updates to various audio device properties are not handled // correctly. CFRunLoopRef theRunLoop = NULL; AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; error( RtAudioError::WARNING ); } #endif } RtApiCore :: ~RtApiCore() { // The subclass destructor gets called before the base class // destructor, so close an existing stream before deallocating // apiDeviceId memory. if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiCore :: getDeviceCount( void ) { // Find out how many audio devices there are, if any. UInt32 dataSize; AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; error( RtAudioError::WARNING ); return 0; } return dataSize / sizeof( AudioDeviceID ); } unsigned int RtApiCore :: getDefaultInputDevice( void ) { unsigned int nDevices = getDeviceCount(); if ( nDevices <= 1 ) return 0; AudioDeviceID id; UInt32 dataSize = sizeof( AudioDeviceID ); AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; error( RtAudioError::WARNING ); return 0; } dataSize *= nDevices; AudioDeviceID deviceList[ nDevices ]; property.mSelector = kAudioHardwarePropertyDevices; result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; error( RtAudioError::WARNING ); return 0; } for ( unsigned int i=0; i= nDevices ) { errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } AudioDeviceID deviceList[ nDevices ]; UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; error( RtAudioError::WARNING ); return info; } AudioDeviceID id = deviceList[ device ]; // Get the device name. info.name.erase(); CFStringRef cfname; dataSize = sizeof( CFStringRef ); property.mSelector = kAudioObjectPropertyManufacturer; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); int length = CFStringGetLength(cfname); char *mname = (char *)malloc(length * 3 + 1); #if defined( UNICODE ) || defined( _UNICODE ) CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); #else CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); #endif info.name.append( (const char *)mname, strlen(mname) ); info.name.append( ": " ); CFRelease( cfname ); free(mname); property.mSelector = kAudioObjectPropertyName; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); length = CFStringGetLength(cfname); char *name = (char *)malloc(length * 3 + 1); #if defined( UNICODE ) || defined( _UNICODE ) CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); #else CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); #endif info.name.append( (const char *)name, strlen(name) ); CFRelease( cfname ); free(name); // Get the output stream "configuration". AudioBufferList *bufferList = nil; property.mSelector = kAudioDevicePropertyStreamConfiguration; property.mScope = kAudioDevicePropertyScopeOutput; // property.mElement = kAudioObjectPropertyElementWildcard; dataSize = 0; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; error( RtAudioError::WARNING ); return info; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if ( result != noErr || dataSize == 0 ) { free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get output channel information. unsigned int i, nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // Get the input stream "configuration". property.mScope = kAudioDevicePropertyScopeInput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; error( RtAudioError::WARNING ); return info; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get input channel information. nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Probe the device sample rates. bool isInput = false; if ( info.outputChannels == 0 ) isInput = true; // Determine the supported sample rates. property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != kAudioHardwareNoError || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } UInt32 nRanges = dataSize / sizeof( AudioValueRange ); AudioValueRange rangeList[ nRanges ]; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); if ( result != kAudioHardwareNoError ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // The sample rate reporting mechanism is a bit of a mystery. It // seems that it can either return individual rates or a range of // rates. I assume that if the min / max range values are the same, // then that represents a single supported rate and if the min / max // range values are different, the device supports an arbitrary // range of values (though there might be multiple ranges, so we'll // use the most conservative range). Float64 minimumRate = 1.0, maximumRate = 10000000000.0; bool haveValueRange = false; info.sampleRates.clear(); for ( UInt32 i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = tmpSr; } else { haveValueRange = true; if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; } } if ( haveValueRange ) { for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } // Sort and remove any redundant values std::sort( info.sampleRates.begin(), info.sampleRates.end() ); info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // CoreAudio always uses 32-bit floating point data for PCM streams. // Thus, any other "physical" formats supported by the device are of // no interest to the client. info.nativeFormats = RTAUDIO_FLOAT32; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; info.probed = true; return info; } static OSStatus callbackHandler( AudioDeviceID inDevice, const AudioTimeStamp* /*inNow*/, const AudioBufferList* inInputData, const AudioTimeStamp* /*inInputTime*/, AudioBufferList* outOutputData, const AudioTimeStamp* /*inOutputTime*/, void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiCore *object = (RtApiCore *) info->object; if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) return kAudioHardwareUnspecifiedError; else return kAudioHardwareNoError; } static OSStatus xrunListener( AudioObjectID /*inDevice*/, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void* handlePointer ) { CoreHandle *handle = (CoreHandle *) handlePointer; for ( UInt32 i=0; ixrun[1] = true; else handle->xrun[0] = true; } } return kAudioHardwareNoError; } static OSStatus rateListener( AudioObjectID inDevice, UInt32 /*nAddresses*/, const AudioObjectPropertyAddress /*properties*/[], void* ratePointer ) { Float64 *rate = (Float64 *) ratePointer; UInt32 dataSize = sizeof( Float64 ); AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); return kAudioHardwareNoError; } bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { // Get device ID unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; return FAILURE; } AudioDeviceID deviceList[ nDevices ]; UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; return FAILURE; } AudioDeviceID id = deviceList[ device ]; // Setup for stream mode. bool isInput = false; if ( mode == INPUT ) { isInput = true; property.mScope = kAudioDevicePropertyScopeInput; } else property.mScope = kAudioDevicePropertyScopeOutput; // Get the stream "configuration". AudioBufferList *bufferList = nil; dataSize = 0; property.mSelector = kAudioDevicePropertyStreamConfiguration; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList."; return FAILURE; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Search for one or more streams that contain the desired number of // channels. CoreAudio devices can have an arbitrary number of // streams and each stream can have an arbitrary number of channels. // For each stream, a single buffer of interleaved samples is // provided. RtAudio prefers the use of one stream of interleaved // data or multiple consecutive single-channel streams. However, we // now support multiple consecutive multi-channel streams of // interleaved data as well. UInt32 iStream, offsetCounter = firstChannel; UInt32 nStreams = bufferList->mNumberBuffers; bool monoMode = false; bool foundStream = false; // First check that the device supports the requested number of // channels. UInt32 deviceChannels = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( deviceChannels < ( channels + firstChannel ) ) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; errorText_ = errorStream_.str(); return FAILURE; } // Look for a single stream meeting our needs. UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels >= channels + offsetCounter ) { firstStream = iStream; channelOffset = offsetCounter; foundStream = true; break; } if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } // If we didn't find a single stream above, then we should be able // to meet the channel specification with multiple streams. if ( foundStream == false ) { monoMode = true; offsetCounter = firstChannel; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } firstStream = iStream; channelOffset = offsetCounter; Int32 channelCounter = channels + offsetCounter - streamChannels; if ( streamChannels > 1 ) monoMode = false; while ( channelCounter > 0 ) { streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; if ( streamChannels > 1 ) monoMode = false; channelCounter -= streamChannels; streamCount++; } } free( bufferList ); // Determine the buffer size. AudioValueRange bufferRange; dataSize = sizeof( AudioValueRange ); property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; // Set the buffer size. For multiple streams, I'm assuming we only // need to make this setting for the master channel. UInt32 theSize = (UInt32) *bufferSize; dataSize = sizeof( UInt32 ); property.mSelector = kAudioDevicePropertyBufferFrameSize; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! *bufferSize = theSize; if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; // Try to set "hog" mode ... it's not clear to me this is working. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { pid_t hog_pid; dataSize = sizeof( hog_pid ); property.mSelector = kAudioDevicePropertyHogMode; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } if ( hog_pid != getpid() ) { hog_pid = getpid(); result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } } } // Check and if necessary, change the sample rate for the device. Float64 nominalRate; dataSize = sizeof( Float64 ); property.mSelector = kAudioDevicePropertyNominalSampleRate; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; errorText_ = errorStream_.str(); return FAILURE; } // Only change the sample rate if off by more than 1 Hz. if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { // Set a property listener for the sample rate change Float64 reportedRate = 0.0; AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } nominalRate = (Float64) sampleRate; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); if ( result != noErr ) { AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Now wait until the reported nominal rate is what we just set. UInt32 microCounter = 0; while ( reportedRate != nominalRate ) { microCounter += 5000; if ( microCounter > 5000000 ) break; usleep( 5000 ); } // Remove the property listener. AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( microCounter > 5000000 ) { errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now set the stream format for all streams. Also, check the // physical format of the device and change that if necessary. AudioStreamBasicDescription description; dataSize = sizeof( AudioStreamBasicDescription ); property.mSelector = kAudioStreamPropertyVirtualFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Set the sample rate and data format id. However, only make the // change if the sample rate is not within 1.0 of the desired // rate and the format is not linear pcm. bool updateFormat = false; if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { description.mSampleRate = (Float64) sampleRate; updateFormat = true; } if ( description.mFormatID != kAudioFormatLinearPCM ) { description.mFormatID = kAudioFormatLinearPCM; updateFormat = true; } if ( updateFormat ) { result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now check the physical format. property.mSelector = kAudioStreamPropertyPhysicalFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } //std::cout << "Current physical stream format:" << std::endl; //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; //std::cout << " sample rate = " << description.mSampleRate << std::endl; if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { description.mFormatID = kAudioFormatLinearPCM; //description.mSampleRate = (Float64) sampleRate; AudioStreamBasicDescription testDescription = description; UInt32 formatFlags; // We'll try higher bit rates first and then work our way down. std::vector< std::pair > physicalFormats; formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; physicalFormats.push_back( std::pair( 32, formatFlags ) ); formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 32, formatFlags ) ); physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low formatFlags |= kAudioFormatFlagIsAlignedHigh; physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 16, formatFlags ) ); physicalFormats.push_back( std::pair( 8, formatFlags ) ); bool setPhysicalFormat = false; for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( streamCount == 1 ) { if ( stream_.nUserChannels[mode] > 1 && stream_.userInterleaved != stream_.deviceInterleaved[mode] ) stream_.doConvertBuffer[mode] = true; } else if ( monoMode && stream_.userInterleaved ) stream_.doConvertBuffer[mode] = true; // Allocate our CoreHandle structure for the stream. CoreHandle *handle = 0; if ( stream_.apiHandle == 0 ) { try { handle = new CoreHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; goto error; } if ( pthread_cond_init( &handle->condition, NULL ) ) { errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else handle = (CoreHandle *) stream_.apiHandle; handle->iStream[mode] = firstStream; handle->nStreams[mode] = streamCount; handle->id[mode] = id; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; goto error; } // If possible, we will make use of the CoreAudio stream buffers as // "device buffers". However, we can't do this if using multiple // streams. if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) { if ( streamCount > 1 ) setConvertInfo( mode, 0 ); else setConvertInfo( mode, channelOffset ); } if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) // Only one callback procedure per device. stream_.mode = DUPLEX; else { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); #else // deprecated in favor of AudioDeviceCreateIOProcID() result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; errorText_ = errorStream_.str(); goto error; } if ( stream_.mode == OUTPUT && mode == INPUT ) stream_.mode = DUPLEX; else stream_.mode = mode; } // Setup the device property listener for over/underload. property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiCore :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if (handle) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], handle->procId[0] ); AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); #else // deprecated behaviour if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], callbackHandler ); AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { if (handle) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], handle->procId[1] ); AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); #else // deprecated behaviour if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], callbackHandler ); AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif } } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // Destroy pthread condition variable. pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiCore :: startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiCore::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStart( handle->id[0], handle->procId[0] ); #else // deprecated behaviour result = AudioDeviceStart( handle->id[0], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStart( handle->id[1], handle->procId[1] ); #else // deprecated behaviour result = AudioDeviceStart( handle->id[1], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == noErr ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStop( handle->id[0], handle->procId[0] ); #else // deprecated behaviour result = AudioDeviceStop( handle->id[0], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStop( handle->id[1], handle->procId[1] ); #else // deprecated behaviour result = AudioDeviceStop( handle->id[1], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } stream_.state = STREAM_STOPPED; unlock: if ( result == noErr ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is better to handle it this way because the // callbackEvent() function probably should return before the AudioDeviceStop() // function is called. static void *coreStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiCore *object = (RtApiCore *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, coreStopStream, info ); else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } AudioDeviceID outputDevice = handle->id[0]; // Invoke user callback to get fresh output data UNLESS we are // draining stream or duplex mode AND the input/output devices are // different AND this function is called for the input device. if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream if ( handle->nStreams[0] == 1 ) { memset( outBufferList->mBuffers[handle->iStream[0]].mData, 0, outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } else { // fill multiple streams with zeros for ( unsigned int i=0; inStreams[0]; i++ ) { memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, 0, outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); } } } else if ( handle->nStreams[0] == 1 ) { if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], stream_.convertInfo[0] ); } else { // copy from user buffer memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } } else { // fill multiple streams Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); inBuffer = (Float32 *) stream_.deviceBuffer; } if ( stream_.deviceInterleaved[0] == false ) { // mono mode UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); } } else { // fill multiple multi-channel streams with interleaved data UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; Float32 *out, *in; bool inInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 inChannels = stream_.nUserChannels[0]; if ( stream_.doConvertBuffer[0] ) { inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode inChannels = stream_.nDeviceChannels[0]; } if ( inInterleaved ) inOffset = 1; else inOffset = stream_.bufferSize; channelsLeft = inChannels; for ( unsigned int i=0; inStreams[0]; i++ ) { in = inBuffer; out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; outJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[0] > 0 ) { streamChannels -= stream_.channelOffset[0]; outJump = stream_.channelOffset[0]; out += outJump; } // Account for possible unfilled channels at end of the last stream if ( streamChannels > channelsLeft ) { outJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine input buffer offsets and skips if ( inInterleaved ) { inJump = inChannels; in += inChannels - channelsLeft; } else { inJump = 1; in += (inChannels - channelsLeft) * inOffset; } for ( unsigned int i=0; idrainCounter ) { handle->drainCounter++; goto unlock; } AudioDeviceID inputDevice; inputDevice = handle->id[1]; if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { if ( handle->nStreams[1] == 1 ) { if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer convertBuffer( stream_.userBuffer[1], (char *) inBufferList->mBuffers[handle->iStream[1]].mData, stream_.convertInfo[1] ); } else { // copy to user buffer memcpy( stream_.userBuffer[1], inBufferList->mBuffers[handle->iStream[1]].mData, inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); } } else { // read from multiple streams Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; if ( stream_.deviceInterleaved[1] == false ) { // mono mode UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); } } else { // read from multiple multi-channel streams UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; Float32 *out, *in; bool outInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 outChannels = stream_.nUserChannels[1]; if ( stream_.doConvertBuffer[1] ) { outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode outChannels = stream_.nDeviceChannels[1]; } if ( outInterleaved ) outOffset = 1; else outOffset = stream_.bufferSize; channelsLeft = outChannels; for ( unsigned int i=0; inStreams[1]; i++ ) { out = outBuffer; in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; inJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[1] > 0 ) { streamChannels -= stream_.channelOffset[1]; inJump = stream_.channelOffset[1]; in += inJump; } // Account for possible unread channels at end of the last stream if ( streamChannels > channelsLeft ) { inJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine output buffer offsets and skips if ( outInterleaved ) { outJump = outChannels; out += outChannels - channelsLeft; } else { outJump = 1; out += (outChannels - channelsLeft) * outOffset; } for ( unsigned int i=0; iid[0] != handle->id[1] && deviceId == handle->id[0] ) ) RtApi::tickStreamTime(); return SUCCESS; } const char* RtApiCore :: getErrorCode( OSStatus code ) { switch( code ) { case kAudioHardwareNotRunningError: return "kAudioHardwareNotRunningError"; case kAudioHardwareUnspecifiedError: return "kAudioHardwareUnspecifiedError"; case kAudioHardwareUnknownPropertyError: return "kAudioHardwareUnknownPropertyError"; case kAudioHardwareBadPropertySizeError: return "kAudioHardwareBadPropertySizeError"; case kAudioHardwareIllegalOperationError: return "kAudioHardwareIllegalOperationError"; case kAudioHardwareBadObjectError: return "kAudioHardwareBadObjectError"; case kAudioHardwareBadDeviceError: return "kAudioHardwareBadDeviceError"; case kAudioHardwareBadStreamError: return "kAudioHardwareBadStreamError"; case kAudioHardwareUnsupportedOperationError: return "kAudioHardwareUnsupportedOperationError"; case kAudioDeviceUnsupportedFormatError: return "kAudioDeviceUnsupportedFormatError"; case kAudioDevicePermissionsError: return "kAudioDevicePermissionsError"; default: return "CoreAudio unknown error"; } } //******************** End of __MACOSX_CORE__ *********************// #endif #if defined(__UNIX_JACK__) // JACK is a low-latency audio server, originally written for the // GNU/Linux operating system and now also ported to OS-X. It can // connect a number of different applications to an audio device, as // well as allowing them to share audio between themselves. // // When using JACK with RtAudio, "devices" refer to JACK clients that // have ports connected to the server. The JACK server is typically // started in a terminal as follows: // // .jackd -d alsa -d hw:0 // // or through an interface program such as qjackctl. Many of the // parameters normally set for a stream are fixed by the JACK server // and can be specified when the JACK server is started. In // particular, // // .jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4 // // specifies a sample rate of 44100 Hz, a buffer size of 512 sample // frames, and number of buffers = 4. Once the server is running, it // is not possible to override these values. If the values are not // specified in the command-line, the JACK server uses default values. // // The JACK server does not have to be running when an instance of // RtApiJack is created, though the function getDeviceCount() will // report 0 devices found until JACK has been started. When no // devices are available (i.e., the JACK server is not running), a // stream cannot be opened. #include #include #include // A structure to hold various information related to the Jack API // implementation. struct JackHandle { jack_client_t *client; jack_port_t **ports[2]; std::string deviceName[2]; bool xrun[2]; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. JackHandle() :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; #if !defined(__RTAUDIO_DEBUG__) static void jackSilentError( const char * ) {}; #endif RtApiJack :: RtApiJack() :shouldAutoconnect_(true) { // Nothing to do here. #if !defined(__RTAUDIO_DEBUG__) // Turn off Jack's internal error reporting. jack_set_error_function( &jackSilentError ); #endif } RtApiJack :: ~RtApiJack() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiJack :: getDeviceCount( void ) { // See if we can become a jack client. jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); if ( client == 0 ) return 0; const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nChannels ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon + 1 ); if ( port != previousPort ) { nDevices++; previousPort = port; } } } while ( ports[++nChannels] ); free( ports ); } jack_client_close( client ); return nDevices; } RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); if ( client == 0 ) { errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; error( RtAudioError::WARNING ); return info; } const char **ports; std::string port, previousPort; unsigned int nPorts = 0, nDevices = 0; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nPorts ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { if ( nDevices == device ) info.name = port; nDevices++; previousPort = port; } } } while ( ports[++nPorts] ); free( ports ); } if ( device >= nDevices ) { jack_client_close( client ); errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } // Get the current jack server sample rate. info.sampleRates.clear(); info.preferredSampleRate = jack_get_sample_rate( client ); info.sampleRates.push_back( info.preferredSampleRate ); // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); info.outputChannels = nChannels; } // Jack "output ports" equal RtAudio input channels. nChannels = 0; ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); info.inputChannels = nChannels; } if ( info.outputChannels == 0 && info.inputChannels == 0 ) { jack_client_close(client); errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; error( RtAudioError::WARNING ); return info; } // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Jack always uses 32-bit floats. info.nativeFormats = RTAUDIO_FLOAT32; // Jack doesn't provide default devices so we'll use the first available one. if ( device == 0 && info.outputChannels > 0 ) info.isDefaultOutput = true; if ( device == 0 && info.inputChannels > 0 ) info.isDefaultInput = true; jack_client_close(client); info.probed = true; return info; } static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; return 0; } // This function will be called by a spawned thread when the Jack // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before // the jack_deactivate() function (in closeStream()) will return. static void *jackCloseStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->closeStream(); pthread_exit( NULL ); } static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; // Check current stream state. If stopped, then we'll assume this // was called as a result of a call to RtApiJack::stopStream (the // deactivation of a client handle causes this function to be called). // If not, we'll assume the Jack server is shutting down or some // other problem occurred and we should close the stream. if ( object->isStreamRunning() == false ) return; ThreadHandle threadId; pthread_create( &threadId, NULL, jackCloseStream, info ); std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; } static int jackXrun( void *infoPointer ) { JackHandle *handle = *((JackHandle **) infoPointer); if ( handle->ports[0] ) handle->xrun[0] = true; if ( handle->ports[1] ) handle->xrun[1] = true; return 0; } bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { JackHandle *handle = (JackHandle *) stream_.apiHandle; // Look for jack server and try to become a client (only do once per stream). jack_client_t *client = 0; if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; if ( options && !options->streamName.empty() ) client = jack_client_open( options->streamName.c_str(), jackoptions, status ); else client = jack_client_open( "RtApiJack", jackoptions, status ); if ( client == 0 ) { errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; error( RtAudioError::WARNING ); return FAILURE; } } else { // The handle must have been created on an earlier pass. client = handle->client; } const char **ports; std::string port, previousPort, deviceName; unsigned int nPorts = 0, nDevices = 0; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nPorts ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { if ( nDevices == device ) deviceName = port; nDevices++; previousPort = port; } } } while ( ports[++nPorts] ); free( ports ); } if ( device >= nDevices ) { errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; return FAILURE; } unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; if ( ! (options && (options->flags & RTAUDIO_JACK_DONT_CONNECT)) ) { // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); } // Compare the jack ports for specified client to the requested number of channels. if ( nChannels < (channels + firstChannel) ) { errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Check the jack server sample rate. unsigned int jackRate = jack_get_sample_rate( client ); if ( sampleRate != jackRate ) { jack_client_close( client ); errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = jackRate; // Get the latency of the JACK port. ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); // the range (usually the min and max are equal) jack_latency_range_t latrange; latrange.min = latrange.max = 0; // get the latency range jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); // be optimistic, use the min! stream_.latency[mode] = latrange.min; //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); } free( ports ); // The jack server always uses 32-bit floating-point data. stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; stream_.userFormat = format; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Jack always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Jack always provides host byte-ordered data. stream_.doByteSwap[mode] = false; // Get the buffer size. The buffer size and number of buffers // (periods) is set when the jack server is started. stream_.bufferSize = (int) jack_get_buffer_size( client ); *bufferSize = stream_.bufferSize; stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate our JackHandle structure for the stream. if ( handle == 0 ) { try { handle = new JackHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; goto error; } if ( pthread_cond_init(&handle->condition, NULL) ) { errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; handle->client = client; } handle->deviceName[mode] = deviceName; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; if ( mode == OUTPUT ) bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); else { // mode == INPUT bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); if ( bufferBytes < bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate memory for the Jack ports (channels) identifiers. handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); if ( handle->ports[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; goto error; } stream_.device[mode] = device; stream_.channelOffset[mode] = firstChannel; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up the stream for output. stream_.mode = DUPLEX; else { stream_.mode = mode; jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); } // Register our ports. char label[64]; if ( mode == OUTPUT ) { for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); } } else { for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); } } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); if ( options && options->flags & RTAUDIO_JACK_DONT_CONNECT ) shouldAutoconnect_ = false; return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); jack_client_close( handle->client ); if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } return FAILURE; } void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( handle ) { if ( stream_.state == STREAM_RUNNING ) jack_deactivate( handle->client ); jack_client_close( handle->client ); } if ( handle ) { if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiJack :: startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiJack::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; goto unlock; } const char **ports; // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { result = 1; ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; } // Now make the port connections. Since RtAudio wasn't designed to // allow the user to select particular channels of a device, we'll // just open the first "nChannels" ports with offset. for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting output ports!"; goto unlock; } } free(ports); } if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { result = 1; ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; } // Now make the port connections. See note above. for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting input ports!"; goto unlock; } } free(ports); } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiJack :: stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } } jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; } void RtApiJack :: abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the jack_deactivate() // function will return. static void *jackStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiJack :: callbackEvent( unsigned long nframes ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; JackHandle *handle = (JackHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); else pthread_cond_signal( &handle->condition ); return SUCCESS; } // Invoke user callback first, to get fresh output data. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; ThreadHandle id; pthread_create( &id, NULL, jackStopStream, info ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } jack_default_audio_sample_t *jackbuffer; unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memset( jackbuffer, 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); } } else { // no buffer conversion for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[1] ) { for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); } convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { // no buffer conversion for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); } } } unlock: RtApi::tickStreamTime(); return SUCCESS; } //******************** End of __UNIX_JACK__ *********************// #endif #if defined(__WINDOWS_ASIO__) // ASIO API on Windows // The ASIO API is designed around a callback scheme, so this // implementation is similar to that used for OS-X CoreAudio and Linux // Jack. The primary constraint with ASIO is that it only allows // access to a single driver at a time. Thus, it is not possible to // have more than one simultaneous RtAudio stream. // // This implementation also requires a number of external ASIO files // and a few global variables. The ASIO callback scheme does not // allow for the passing of user data, so we must create a global // pointer to our callbackInfo structure. // // On unix systems, we make use of a pthread condition variable. // Since there is no equivalent in Windows, I hacked something based // on information found in // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. #include "asiosys.h" #include "asio.h" #include "iasiothiscallresolver.h" #include "asiodrivers.h" #include static AsioDrivers drivers; static ASIOCallbacks asioCallbacks; static ASIODriverInfo driverInfo; static CallbackInfo *asioCallbackInfo; static bool asioXRun; struct AsioHandle { int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. ASIOBufferInfo *bufferInfos; HANDLE condition; AsioHandle() :drainCounter(0), internalDrain(false), bufferInfos(0) {} }; // Function declarations (definitions at end of section) static const char* getAsioErrorString( ASIOError result ); static void sampleRateChanged( ASIOSampleRate sRate ); static long asioMessages( long selector, long value, void* message, double* opt ); RtApiAsio :: RtApiAsio() { // ASIO cannot run on a multi-threaded apartment. You can call // CoInitialize beforehand, but it must be for apartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded apartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RtAudioError::WARNING ); } coInitialized_ = true; drivers.removeCurrentDriver(); driverInfo.asioVersion = 2; // See note in DirectSound implementation about GetDesktopWindow(). driverInfo.sysRef = GetForegroundWindow(); } RtApiAsio :: ~RtApiAsio() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); } unsigned int RtApiAsio :: getDeviceCount( void ) { return (unsigned int) drivers.asioGetNumDev(); } // We can only load one ASIO driver, so the default output is always the first device. unsigned int RtApiAsio :: getDefaultOutputDevice( void ) { return 0; } // We can only load one ASIO driver, so the default input is always the first device. unsigned int RtApiAsio :: getDefaultInputDevice( void ) { return 0; } RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; // Get device ID unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } // If a stream is already open, we cannot probe other devices. Thus, use the saved results. if ( stream_.state != STREAM_CLOSED ) { if ( device >= devices_.size() ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; error( RtAudioError::WARNING ); return info; } return devices_[ device ]; } char driverName[32]; ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.name = driverName; if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Determine the device channel information. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.outputChannels = outputChannels; info.inputChannels = inputChannels; if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Determine the supported sample rates. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } // Determine supported data types ... just check first channel and assume rest are the same. ASIOChannelInfo channelInfo; channelInfo.channel = 0; channelInfo.isInput = true; if ( info.inputChannels <= 0 ) channelInfo.isInput = false; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.nativeFormats = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) info.nativeFormats |= RTAUDIO_SINT16; else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) info.nativeFormats |= RTAUDIO_SINT32; else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) info.nativeFormats |= RTAUDIO_FLOAT32; else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) info.nativeFormats |= RTAUDIO_FLOAT64; else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) info.nativeFormats |= RTAUDIO_SINT24; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; info.probed = true; drivers.removeCurrentDriver(); return info; } static void bufferSwitch( long index, ASIOBool /*processNow*/ ) { RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; object->callbackEvent( index ); } void RtApiAsio :: saveDeviceInfo( void ) { devices_.clear(); unsigned int nDevices = getDeviceCount(); devices_.resize( nDevices ); for ( unsigned int i=0; isaveDeviceInfo(); if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // keep them before any "goto error", they are used for error cleanup + goto device boundary checks bool buffersAllocated = false; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; unsigned int nChannels; // Check the device channel count. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; errorText_ = errorStream_.str(); goto error; } stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; // Verify the sample rate is supported. result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } // Get the current sample rate ASIOSampleRate currentRate; result = ASIOGetSampleRate( ¤tRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; errorText_ = errorStream_.str(); goto error; } // Set the sample rate only if necessary if ( currentRate != sampleRate ) { result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } } // Determine the driver data type. ASIOChannelInfo channelInfo; channelInfo.channel = 0; if ( mode == OUTPUT ) channelInfo.isInput = false; else channelInfo.isInput = true; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; errorText_ = errorStream_.str(); goto error; } // Assuming WINDOWS host is always little-endian. stream_.doByteSwap[mode] = false; stream_.userFormat = format; stream_.deviceFormat[mode] = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; } if ( stream_.deviceFormat[mode] == 0 ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); goto error; } // Set the buffer size. For a duplex stream, this will end up // setting the buffer size based on the input constraints, which // should be ok. long minSize, maxSize, preferSize, granularity; result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; errorText_ = errorStream_.str(); goto error; } if ( isDuplexInput ) { // When this is the duplex input (output was opened before), then we have to use the same // buffersize as the output, because it might use the preferred buffer size, which most // likely wasn't passed as input to this. The buffer sizes have to be identically anyway, // So instead of throwing an error, make them equal. The caller uses the reference // to the "bufferSize" param as usual to set up processing buffers. *bufferSize = stream_.bufferSize; } else { if ( *bufferSize == 0 ) *bufferSize = preferSize; else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; else if ( granularity == -1 ) { // Make sure bufferSize is a power of two. int log2_of_min_size = 0; int log2_of_max_size = 0; for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { if ( minSize & ((long)1 << i) ) log2_of_min_size = i; if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; } long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); int min_delta_num = log2_of_min_size; for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); if (current_delta < min_delta) { min_delta = current_delta; min_delta_num = i; } } *bufferSize = ( (unsigned int)1 << min_delta_num ); if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; } else if ( granularity != 0 ) { // Set to an even multiple of granularity, rounding up. *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; } } /* // we don't use it anymore, see above! // Just left it here for the case... if ( isDuplexInput && stream_.bufferSize != *bufferSize ) { errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; goto error; } */ stream_.bufferSize = *bufferSize; stream_.nBuffers = 2; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // ASIO always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Allocate, if necessary, our AsioHandle structure for the stream. if ( handle == 0 ) { try { handle = new AsioHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } handle->bufferInfos = 0; // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } // Create the ASIO internal buffers. Since RtAudio sets up input // and output separately, we'll have to dispose of previously // created output buffers for a duplex stream. if ( mode == INPUT && stream_.mode == OUTPUT ) { ASIODisposeBuffers(); if ( handle->bufferInfos ) free( handle->bufferInfos ); } // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. unsigned int i; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); if ( handle->bufferInfos == NULL ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } ASIOBufferInfo *infos; infos = handle->bufferInfos; for ( i=0; iisInput = ASIOFalse; infos->channelNum = i + stream_.channelOffset[0]; infos->buffers[0] = infos->buffers[1] = 0; } for ( i=0; iisInput = ASIOTrue; infos->channelNum = i + stream_.channelOffset[1]; infos->buffers[0] = infos->buffers[1] = 0; } // prepare for callbacks stream_.sampleRate = sampleRate; stream_.device[mode] = device; stream_.mode = isDuplexInput ? DUPLEX : mode; // store this class instance before registering callbacks, that are going to use it asioCallbackInfo = &stream_.callbackInfo; stream_.callbackInfo.object = (void *) this; // Set up the ASIO callback structure and create the ASIO data buffers. asioCallbacks.bufferSwitch = &bufferSwitch; asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; asioCallbacks.bufferSwitchTimeInfo = NULL; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges // but only accept the preferred buffer size as parameter for ASIOCreateBuffers (e.g. Creative's ASIO driver). // In that case, let's be naïve and try that instead. *bufferSize = preferSize; stream_.bufferSize = *bufferSize; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); } if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; errorText_ = errorStream_.str(); goto error; } buffersAllocated = true; stream_.state = STREAM_STOPPED; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( isDuplexInput && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Determine device latencies long inputLatency, outputLatency; result = ASIOGetLatencies( &inputLatency, &outputLatency ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; stream_.latency[1] = inputLatency; } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); return SUCCESS; error: if ( !isDuplexInput ) { // the cleanup for error in the duplex input, is done by RtApi::openStream // So we clean up for single channel only if ( buffersAllocated ) ASIODisposeBuffers(); drivers.removeCurrentDriver(); if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } if ( stream_.userBuffer[mode] ) { free( stream_.userBuffer[mode] ); stream_.userBuffer[mode] = 0; } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } } return FAILURE; }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; ASIOStop(); } ASIODisposeBuffers(); drivers.removeCurrentDriver(); AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } bool stopThreadCalled = false; void RtApiAsio :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAsio::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; errorText_ = errorStream_.str(); goto unlock; } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; asioXRun = false; unlock: stopThreadCalled = false; if ( result == ASE_OK ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } } stream_.state = STREAM_STOPPED; ASIOError result = ASIOStop(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; errorText_ = errorStream_.str(); } if ( result == ASE_OK ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } // The following lines were commented-out because some behavior was // noted where the device buffers need to be zeroed to avoid // continuing sound, even when the device buffers are completely // disposed. So now, calling abort is the same as calling stop. // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the ASIOStop() // function will return. static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; object->stopStream(); _endthreadex( 0 ); return 0; } bool RtApiAsio :: callbackEvent( long bufferIndex ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // Check if we were draining the stream and signal if finished. if ( handle->drainCounter > 3 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else { // spawn a thread to stop the stream unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); } return SUCCESS; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && asioXRun == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; asioXRun = false; } if ( stream_.mode != OUTPUT && asioXRun == true ) { status |= RTAUDIO_INPUT_OVERFLOW; asioXRun = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } unsigned int nChannels, bufferBytes, i, j; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[0], stream_.deviceFormat[0] ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); } } else { if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.userBuffer[0], stream_.bufferSize * stream_.nUserChannels[0], stream_.userFormat ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); if (stream_.doConvertBuffer[1]) { // Always interleave ASIO input data. for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) memcpy( &stream_.deviceBuffer[j++*bufferBytes], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[1], stream_.deviceFormat[1] ); convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { memcpy( &stream_.userBuffer[1][bufferBytes*j++], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.userBuffer[1], stream_.bufferSize * stream_.nUserChannels[1], stream_.userFormat ); } } unlock: // The following call was suggested by Malte Clasen. While the API // documentation indicates it should not be required, some device // drivers apparently do not function correctly without it. ASIOOutputReady(); RtApi::tickStreamTime(); return SUCCESS; } static void sampleRateChanged( ASIOSampleRate sRate ) { // The ASIO documentation says that this usually only happens during // external sync. Audio processing is not stopped by the driver, // actual sample rate might not have even changed, maybe only the // sample rate status of an AES/EBU or S/PDIF digital input at the // audio device. RtApi *object = (RtApi *) asioCallbackInfo->object; try { object->stopStream(); } catch ( RtAudioError &exception ) { std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; return; } std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; } static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) { long ret = 0; switch( selector ) { case kAsioSelectorSupported: if ( value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest || value == kAsioLatenciesChanged // The following three were added for ASIO 2.0, you don't // necessarily have to support them. || value == kAsioSupportsTimeInfo || value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor) ret = 1L; break; case kAsioResetRequest: // Defer the task and perform the reset of the driver during the // next "safe" situation. You cannot reset the driver right now, // as this code is called from the driver. Reset the driver is // done by completely destruct is. I.e. ASIOStop(), // ASIODisposeBuffers(), Destruction Afterwards you initialize the // driver again. std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; ret = 1L; break; case kAsioResyncRequest: // This informs the application that the driver encountered some // non-fatal data loss. It is used for synchronization purposes // of different media. Added mainly to work around the Win16Mutex // problems in Windows 95/98 with the Windows Multimedia system, // which could lose data because the Mutex was held too long by // another thread. However a driver can issue it in other // situations, too. // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; asioXRun = true; ret = 1L; break; case kAsioLatenciesChanged: // This will inform the host application that the drivers were // latencies changed. Beware, it this does not mean that the // buffer sizes have changed! You might need to update internal // delay data. std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; ret = 1L; break; case kAsioEngineVersion: // Return the supported ASIO version of the host application. If // a host application does not implement this selector, ASIO 1.0 // is assumed by the driver. ret = 2L; break; case kAsioSupportsTimeInfo: // Informs the driver whether the // asioCallbacks.bufferSwitchTimeInfo() callback is supported. // For compatibility with ASIO 1.0 drivers the host application // should always support the "old" bufferSwitch method, too. ret = 0; break; case kAsioSupportsTimeCode: // Informs the driver whether application is interested in time // code info. If an application does not need to know about time // code, the driver has less work to do. ret = 0; break; } return ret; } static const char* getAsioErrorString( ASIOError result ) { struct Messages { ASIOError value; const char*message; }; static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, { ASE_InvalidParameter, "Invalid input parameter." }, { ASE_InvalidMode, "Invalid mode." }, { ASE_SPNotAdvancing, "Sample position not advancing." }, { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, { ASE_NoMemory, "Not enough memory to complete the request." } }; for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) if ( m[i].value == result ) return m[i].message; return "Unknown error."; } //******************** End of __WINDOWS_ASIO__ *********************// #endif #if defined(__WINDOWS_WASAPI__) // Windows WASAPI API // Authored by Marcus Tomlinson , April 2014 // - Introduces support for the Windows WASAPI API // - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required // - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface // - Includes automatic internal conversion of sample rate and buffer size between hardware and the user #ifndef INITGUID #define INITGUID #endif #include #include #include #include #include #include #include #include #include #ifndef MF_E_TRANSFORM_NEED_MORE_INPUT #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) #endif #ifndef MFSTARTUP_NOSOCKET #define MFSTARTUP_NOSOCKET 0x1 #endif #ifdef _MSC_VER #pragma comment( lib, "ksuser" ) #pragma comment( lib, "mfplat.lib" ) #pragma comment( lib, "mfuuid.lib" ) #pragma comment( lib, "wmcodecdspuuid" ) #endif //============================================================================= #define SAFE_RELEASE( objectPtr )\ if ( objectPtr )\ {\ objectPtr->Release();\ objectPtr = NULL;\ } typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); #ifndef __IAudioClient3_INTERFACE_DEFINED__ MIDL_INTERFACE( "00000000-0000-0000-0000-000000000000" ) IAudioClient3 { virtual HRESULT GetSharedModeEnginePeriod( WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32* ) = 0; virtual HRESULT InitializeSharedAudioStream( DWORD, UINT32, WAVEFORMATEX*, LPCGUID ) = 0; }; #ifdef __CRT_UUID_DECL __CRT_UUID_DECL( IAudioClient3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) #endif #endif //----------------------------------------------------------------------------- // WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. // Therefore we must perform all necessary conversions to user buffers in order to satisfy these // requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to // provide intermediate storage for read / write synchronization. class WasapiBuffer { public: WasapiBuffer() : buffer_( NULL ), bufferSize_( 0 ), inIndex_( 0 ), outIndex_( 0 ) {} ~WasapiBuffer() { free( buffer_ ); } // sets the length of the internal ring buffer void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { free( buffer_ ); buffer_ = ( char* ) calloc( bufferSize, formatBytes ); bufferSize_ = bufferSize; inIndex_ = 0; outIndex_ = 0; } // attempt to push a buffer into the ring buffer at the current "in" index bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relOutIndex = outIndex_; unsigned int inIndexEnd = inIndex_ + bufferSize; if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { relOutIndex += bufferSize_; } // the "IN" index CAN BEGIN at the "OUT" index // the "IN" index CANNOT END at the "OUT" index if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { return false; // not enough space between "in" index and "out" index } // copy buffer from external to internal int fromZeroSize = inIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromInSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); break; } // update "in" index inIndex_ += bufferSize; inIndex_ %= bufferSize_; return true; } // attempt to pull a buffer from the ring buffer from the current "out" index bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relInIndex = inIndex_; unsigned int outIndexEnd = outIndex_ + bufferSize; if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { relInIndex += bufferSize_; } // the "OUT" index CANNOT BEGIN at the "IN" index // the "OUT" index CAN END at the "IN" index if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } // copy buffer from internal to external int fromZeroSize = outIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromOutSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); break; } // update "out" index outIndex_ += bufferSize; outIndex_ %= bufferSize_; return true; } private: char* buffer_; unsigned int bufferSize_; unsigned int inIndex_; unsigned int outIndex_; }; //----------------------------------------------------------------------------- // In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate // between HW and the user. The WasapiResampler class is used to perform this conversion between // HwIn->UserIn and UserOut->HwOut during the stream callback loop. class WasapiResampler { public: WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, unsigned int inSampleRate, unsigned int outSampleRate ) : _bytesPerSample( bitsPerSample / 8 ) , _channelCount( channelCount ) , _sampleRatio( ( float ) outSampleRate / inSampleRate ) , _transformUnk( NULL ) , _transform( NULL ) , _mediaType( NULL ) , _inputMediaType( NULL ) , _outputMediaType( NULL ) #ifdef __IWMResamplerProps_FWD_DEFINED__ , _resamplerProps( NULL ) #endif { // 1. Initialization MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); // 2. Create Resampler Transform Object CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, ( void** ) &_transformUnk ); _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); #ifdef __IWMResamplerProps_FWD_DEFINED__ _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality #endif // 3. Specify input / output format MFCreateMediaType( &_mediaType ); _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); MFCreateMediaType( &_inputMediaType ); _mediaType->CopyAllItems( _inputMediaType ); _transform->SetInputType( 0, _inputMediaType, 0 ); MFCreateMediaType( &_outputMediaType ); _mediaType->CopyAllItems( _outputMediaType ); _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); _transform->SetOutputType( 0, _outputMediaType, 0 ); // 4. Send stream start messages to Resampler _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); } ~WasapiResampler() { // 8. Send stream stop messages to Resampler _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); // 9. Cleanup MFShutdown(); SAFE_RELEASE( _transformUnk ); SAFE_RELEASE( _transform ); SAFE_RELEASE( _mediaType ); SAFE_RELEASE( _inputMediaType ); SAFE_RELEASE( _outputMediaType ); #ifdef __IWMResamplerProps_FWD_DEFINED__ SAFE_RELEASE( _resamplerProps ); #endif } void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount, int maxOutSampleCount = -1 ) { unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; if ( _sampleRatio == 1 ) { // no sample rate conversion required memcpy( outBuffer, inBuffer, inputBufferSize ); outSampleCount = inSampleCount; return; } unsigned int outputBufferSize = 0; if ( maxOutSampleCount != -1 ) { outputBufferSize = _bytesPerSample * _channelCount * maxOutSampleCount; } else { outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); } IMFMediaBuffer* rInBuffer; IMFSample* rInSample; BYTE* rInByteBuffer = NULL; // 5. Create Sample object from input data MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); memcpy( rInByteBuffer, inBuffer, inputBufferSize ); rInBuffer->Unlock(); rInByteBuffer = NULL; rInBuffer->SetCurrentLength( inputBufferSize ); MFCreateSample( &rInSample ); rInSample->AddBuffer( rInBuffer ); // 6. Pass input data to Resampler _transform->ProcessInput( 0, rInSample, 0 ); SAFE_RELEASE( rInBuffer ); SAFE_RELEASE( rInSample ); // 7. Perform sample rate conversion IMFMediaBuffer* rOutBuffer = NULL; BYTE* rOutByteBuffer = NULL; MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; DWORD rStatus; DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput // 7.1 Create Sample object for output data memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); MFCreateSample( &( rOutDataBuffer.pSample ) ); MFCreateMemoryBuffer( rBytes, &rOutBuffer ); rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); rOutDataBuffer.dwStreamID = 0; rOutDataBuffer.dwStatus = 0; rOutDataBuffer.pEvents = NULL; // 7.2 Get output data from Resampler if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) { outSampleCount = 0; SAFE_RELEASE( rOutBuffer ); SAFE_RELEASE( rOutDataBuffer.pSample ); return; } // 7.3 Write output data to outBuffer SAFE_RELEASE( rOutBuffer ); rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); rOutBuffer->GetCurrentLength( &rBytes ); rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); memcpy( outBuffer, rOutByteBuffer, rBytes ); rOutBuffer->Unlock(); rOutByteBuffer = NULL; outSampleCount = rBytes / _bytesPerSample / _channelCount; SAFE_RELEASE( rOutBuffer ); SAFE_RELEASE( rOutDataBuffer.pSample ); } private: unsigned int _bytesPerSample; unsigned int _channelCount; float _sampleRatio; IUnknown* _transformUnk; IMFTransform* _transform; IMFMediaType* _mediaType; IMFMediaType* _inputMediaType; IMFMediaType* _outputMediaType; #ifdef __IWMResamplerProps_FWD_DEFINED__ IWMResamplerProps* _resamplerProps; #endif }; //----------------------------------------------------------------------------- // A structure to hold various information related to the WASAPI implementation. struct WasapiHandle { IAudioClient* captureAudioClient; IAudioClient* renderAudioClient; IAudioCaptureClient* captureClient; IAudioRenderClient* renderClient; HANDLE captureEvent; HANDLE renderEvent; WasapiHandle() : captureAudioClient( NULL ), renderAudioClient( NULL ), captureClient( NULL ), renderClient( NULL ), captureEvent( NULL ), renderEvent( NULL ) {} }; //============================================================================= RtApiWasapi::RtApiWasapi() : coInitialized_( false ), deviceEnumerator_( NULL ) { // WASAPI can run either apartment or multi-threaded HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; // Instantiate device enumerator hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); // If this runs on an old Windows, it will fail. Ignore and proceed. if ( FAILED( hr ) ) deviceEnumerator_ = NULL; } //----------------------------------------------------------------------------- RtApiWasapi::~RtApiWasapi() { if ( stream_.state != STREAM_CLOSED ) closeStream(); SAFE_RELEASE( deviceEnumerator_ ); // If this object previously called CoInitialize() if ( coInitialized_ ) CoUninitialize(); } //============================================================================= unsigned int RtApiWasapi::getDeviceCount( void ) { unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; if ( !deviceEnumerator_ ) return 0; // Count capture devices errorText_.clear(); HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; goto Exit; } Exit: // release all references SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); if ( errorText_.empty() ) return captureDeviceCount + renderDeviceCount; error( RtAudioError::DRIVER_ERROR ); return 0; } //----------------------------------------------------------------------------- RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; std::string defaultDeviceName; bool isCaptureDevice = false; PROPVARIANT deviceNameProp; PROPVARIANT defaultDeviceNameProp; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; IMMDevice* defaultDevicePtr = NULL; IAudioClient* audioClient = NULL; IPropertyStore* devicePropStore = NULL; IPropertyStore* defaultDevicePropStore = NULL; WAVEFORMATEX* deviceFormat = NULL; WAVEFORMATEX* closestMatchFormat = NULL; // probed info.probed = false; // Count capture devices errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; goto Exit; } // validate device index if ( device >= captureDeviceCount + renderDeviceCount ) { errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; errorType = RtAudioError::INVALID_USE; goto Exit; } // determine whether index falls within capture or render devices if ( device >= renderDeviceCount ) { hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; goto Exit; } isCaptureDevice = true; } else { hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; goto Exit; } isCaptureDevice = false; } // get default device name if ( isCaptureDevice ) { hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; goto Exit; } } else { hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; goto Exit; } } hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; goto Exit; } PropVariantInit( &defaultDeviceNameProp ); hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; goto Exit; } defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal); // name hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; goto Exit; } PropVariantInit( &deviceNameProp ); hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; goto Exit; } info.name =convertCharPointerToStdString(deviceNameProp.pwszVal); // is default if ( isCaptureDevice ) { info.isDefaultInput = info.name == defaultDeviceName; info.isDefaultOutput = false; } else { info.isDefaultInput = false; info.isDefaultOutput = info.name == defaultDeviceName; } // channel count hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; goto Exit; } hr = audioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; goto Exit; } if ( isCaptureDevice ) { info.inputChannels = deviceFormat->nChannels; info.outputChannels = 0; info.duplexChannels = 0; } else { info.inputChannels = 0; info.outputChannels = deviceFormat->nChannels; info.duplexChannels = 0; } // sample rates info.sampleRates.clear(); // allow support for all sample rates as we have a built-in sample rate converter for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { info.sampleRates.push_back( SAMPLE_RATES[i] ); } info.preferredSampleRate = deviceFormat->nSamplesPerSec; // native format info.nativeFormats = 0; if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) { if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_FLOAT32; } else if ( deviceFormat->wBitsPerSample == 64 ) { info.nativeFormats |= RTAUDIO_FLOAT64; } } else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) { if ( deviceFormat->wBitsPerSample == 8 ) { info.nativeFormats |= RTAUDIO_SINT8; } else if ( deviceFormat->wBitsPerSample == 16 ) { info.nativeFormats |= RTAUDIO_SINT16; } else if ( deviceFormat->wBitsPerSample == 24 ) { info.nativeFormats |= RTAUDIO_SINT24; } else if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_SINT32; } } // probed info.probed = true; Exit: // release all references PropVariantClear( &deviceNameProp ); PropVariantClear( &defaultDeviceNameProp ); SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); SAFE_RELEASE( defaultDevicePtr ); SAFE_RELEASE( audioClient ); SAFE_RELEASE( devicePropStore ); SAFE_RELEASE( defaultDevicePropStore ); CoTaskMemFree( deviceFormat ); CoTaskMemFree( closestMatchFormat ); if ( !errorText_.empty() ) error( errorType ); return info; } void RtApiWasapi::closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiWasapi::closeStream: No open stream to close."; error( RtAudioError::WARNING ); return; } if ( stream_.state != STREAM_STOPPED ) stopStream(); // clean up stream memory SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); delete ( WasapiHandle* ) stream_.apiHandle; stream_.apiHandle = NULL; for ( int i = 0; i < 2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // update stream state stream_.state = STREAM_CLOSED; } //----------------------------------------------------------------------------- void RtApiWasapi::startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiWasapi::startStream: The stream is already running."; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif // update stream state stream_.state = STREAM_RUNNING; // create WASAPI stream thread stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); if ( !stream_.callbackInfo.thread ) { errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; error( RtAudioError::THREAD_ERROR ); } else { SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); ResumeThread( ( void* ) stream_.callbackInfo.thread ); } } //----------------------------------------------------------------------------- void RtApiWasapi::stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; error( RtAudioError::WARNING ); return; } if ( stream_.state == STREAM_STOPPING ) { errorText_ = "RtApiWasapi::stopStream: The stream is already stopping."; error( RtAudioError::WARNING ); return; } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; error( RtAudioError::THREAD_ERROR ); return; } stream_.callbackInfo.thread = (ThreadHandle) NULL; } //----------------------------------------------------------------------------- void RtApiWasapi::abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; error( RtAudioError::WARNING ); return; } if ( stream_.state == STREAM_STOPPING ) { errorText_ = "RtApiWasapi::abortStream: The stream is already stopping."; error( RtAudioError::WARNING ); return; } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; error( RtAudioError::THREAD_ERROR ); return; } stream_.callbackInfo.thread = (ThreadHandle) NULL; } //----------------------------------------------------------------------------- bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) { bool methodResult = FAILURE; unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; // create API Handle if not already created if ( !stream_.apiHandle ) stream_.apiHandle = ( void* ) new WasapiHandle(); // Count capture devices errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; goto Exit; } // validate device index if ( device >= captureDeviceCount + renderDeviceCount ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; goto Exit; } // if device index falls within capture devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; goto Exit; } // retrieve captureAudioClient from devicePtr IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // if device index falls within render devices and is configured for loopback if ( device < renderDeviceCount && mode == INPUT ) { // if renderAudioClient is not initialised, initialise it now IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( !renderAudioClient ) { probeDeviceOpen( device, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); } // retrieve captureAudioClient from devicePtr IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // if device index falls within render devices and is configured for output if ( device < renderDeviceCount && mode == OUTPUT ) { // if renderAudioClient is already initialised, don't initialise it again IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( renderAudioClient ) { methodResult = SUCCESS; goto Exit; } hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = renderAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // fill stream data if ( ( stream_.mode == OUTPUT && mode == INPUT ) || ( stream_.mode == INPUT && mode == OUTPUT ) ) { stream_.mode = DUPLEX; } else { stream_.mode = mode; } stream_.device[mode] = device; stream_.doByteSwap[mode] = false; stream_.sampleRate = sampleRate; stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); if ( !stream_.userBuffer[mode] ) { errorType = RtAudioError::MEMORY_ERROR; errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; goto Exit; } if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) stream_.callbackInfo.priority = 15; else stream_.callbackInfo.priority = 0; ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode methodResult = SUCCESS; Exit: //clean up SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); CoTaskMemFree( deviceFormat ); // if method failed, close the stream if ( methodResult == FAILURE ) closeStream(); if ( !errorText_.empty() ) error( errorType ); return methodResult; } //============================================================================= DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); return 0; } DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); return 0; } DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); return 0; } //----------------------------------------------------------------------------- void RtApiWasapi::wasapiThread() { // as this is a new thread, we must CoInitialize it CoInitialize( NULL ); HRESULT hr; IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; WAVEFORMATEX* captureFormat = NULL; WAVEFORMATEX* renderFormat = NULL; float captureSrRatio = 0.0f; float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; WasapiResampler* captureResampler = NULL; WasapiResampler* renderResampler = NULL; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; DWORD captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; bool loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT]; bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; // convBuffer is used to store converted buffers between WASAPI and the user char* convBuffer = NULL; unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; std::string errorText; RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibraryW( L"AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } // start capture stream if applicable if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } // init captureResampler captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], captureFormat->nSamplesPerSec, stream_.sampleRate ); captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); if ( !captureClient ) { IAudioClient3* captureAudioClient3 = nullptr; captureAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &captureAudioClient3 ); if ( captureAudioClient3 && !loopbackEnabled ) { UINT32 Ignore; UINT32 MinPeriodInFrames; hr = captureAudioClient3->GetSharedModeEnginePeriod( captureFormat, &Ignore, &Ignore, &MinPeriodInFrames, &Ignore ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, MinPeriodInFrames, captureFormat, NULL ); } else { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, captureFormat, NULL ); } if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } // don't configure captureEvent if in loopback mode if ( !loopbackEnabled ) { // configure captureEvent to trigger on every available capture buffer captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !captureEvent ) { errorType = RtAudioError::SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; goto Exit; } hr = captureAudioClient->SetEventHandle( captureEvent ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; } ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; // reset the capture stream hr = captureAudioClient->Reset(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; goto Exit; } // start the capture stream hr = captureAudioClient->Start(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; goto Exit; } } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } // scale outBufferSize according to stream->user sample rate ratio unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } // init renderResampler renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], stream_.sampleRate, renderFormat->nSamplesPerSec ); renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); if ( !renderClient ) { IAudioClient3* renderAudioClient3 = nullptr; renderAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &renderAudioClient3 ); if ( renderAudioClient3 ) { UINT32 Ignore; UINT32 MinPeriodInFrames; hr = renderAudioClient3->GetSharedModeEnginePeriod( renderFormat, &Ignore, &Ignore, &MinPeriodInFrames, &Ignore ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, MinPeriodInFrames, renderFormat, NULL ); } else { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, renderFormat, NULL ); } if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } // configure renderEvent to trigger on every available render buffer renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RtAudioError::SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; // reset the render stream hr = renderAudioClient->Reset(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; goto Exit; } // start the render stream hr = renderAudioClient->Start(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; goto Exit; } } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } // scale inBufferSize according to user->stream sample rate ratio unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); } // malloc buffer memory if ( stream_.mode == INPUT ) { using namespace std; // for ceilf convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } else if ( stream_.mode == OUTPUT ) { convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } else if ( stream_.mode == DUPLEX ) { convBuffSize = std::max( ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } convBuffSize *= 2; // allow overflow for *SrRatio remainders convBuffer = ( char* ) calloc( convBuffSize, 1 ); stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } // stream process loop while ( stream_.state != STREAM_STOPPING ) { if ( !callbackPulled ) { // Callback Input // ============== // 1. Pull callback buffer from inputBuffer // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count // Convert callback buffer to user format if ( captureAudioClient ) { int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); convBufferSize = 0; while ( convBufferSize < stream_.bufferSize ) { // Pull callback buffer from inputBuffer callbackPulled = captureBuffer.pullBuffer( convBuffer, samplesToPull * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ); if ( !callbackPulled ) { break; } // Convert callback buffer to user sample rate unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); unsigned int convSamples = 0; captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, convBuffer, samplesToPull, convSamples, convBufferSize == 0 ? -1 : stream_.bufferSize - convBufferSize ); convBufferSize += convSamples; samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples } if ( callbackPulled ) { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } else { // no further conversion, simple copy deviceBuffer to userBuffer memcpy( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); } } } else { // if there is no capture stream, set callbackPulled flag callbackPulled = true; } // Execute Callback // ================ // 1. Execute user callback method // 2. Handle return value from callback // if callback has not requested the stream to stop if ( callbackPulled && !callbackStopped ) { // Execute user callback method callbackResult = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, getStreamTime(), captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); // tick stream time RtApi::tickStreamTime(); // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } callbackStopped = true; } else if ( callbackResult == 2 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } callbackStopped = true; } } } // Callback Output // =============== // 1. Convert callback buffer to stream format // 2. Convert callback buffer to stream sample rate and channel count // 3. Push callback buffer into outputBuffer if ( renderAudioClient && callbackPulled ) { // if the last call to renderBuffer.PushBuffer() was successful if ( callbackPushed || convBufferSize == 0 ) { if ( stream_.doConvertBuffer[OUTPUT] ) { // Convert callback buffer to stream format convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); } else { // no further conversion, simple copy userBuffer to deviceBuffer memcpy( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); } // Convert callback buffer to stream sample rate renderResampler->Convert( convBuffer, stream_.deviceBuffer, stream_.bufferSize, convBufferSize ); } // Push callback buffer into outputBuffer callbackPushed = renderBuffer.pushBuffer( convBuffer, convBufferSize * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ); } else { // if there is no render stream, set callbackPushed flag callbackPushed = true; } // Stream Capture // ============== // 1. Get capture buffer from stream // 2. Push capture buffer into inputBuffer // 3. If 2. was successful: Release capture buffer if ( captureAudioClient ) { // if the callback input buffer was not pulled from captureBuffer, wait for next capture event if ( !callbackPulled ) { WaitForSingleObject( loopbackEnabled ? renderEvent : captureEvent, INFINITE ); } // Get capture buffer from stream hr = captureClient->GetBuffer( &streamBuffer, &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } if ( bufferFrameCount != 0 ) { // Push capture buffer into inputBuffer if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ) ) { // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } // Stream Render // ============= // 1. Get render buffer from stream // 2. Pull next buffer from outputBuffer // 3. If 2. was successful: Fill render buffer with next buffer // Release render buffer if ( renderAudioClient ) { // if the callback output buffer was not pushed to renderBuffer, wait for next render event if ( callbackPulled && !callbackPushed ) { WaitForSingleObject( renderEvent, INFINITE ); } // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } bufferFrameCount -= numFramesPadding; if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } // Pull next buffer from outputBuffer // Fill render buffer with next buffer if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ) ) { // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { // unsetting the callbackPulled flag lets the stream know that // the audio device is ready for another callback output buffer. callbackPulled = false; } } Exit: // clean up CoTaskMemFree( captureFormat ); CoTaskMemFree( renderFormat ); free ( convBuffer ); delete renderResampler; delete captureResampler; CoUninitialize(); if ( !errorText.empty() ) { errorText_ = errorText; error( errorType ); } // update stream state stream_.state = STREAM_STOPPED; } //******************** End of __WINDOWS_WASAPI__ *********************// #endif #if defined(__WINDOWS_DS__) // Windows DirectSound API // Modified by Robin Davies, October 2005 // - Improvements to DirectX pointer chasing. // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. // - Auto-call CoInitialize for DSOUND and ASIO platforms. // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 #include #include #include #include #include #include #include #if defined(__MINGW32__) // missing from latest mingw winapi #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #endif #define MINIMUM_DEVICE_BUFFER_SIZE 32768 #ifdef _MSC_VER // if Microsoft Visual C++ #pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. #endif static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) { if ( pointer > bufferSize ) pointer -= bufferSize; if ( laterPointer < earlierPointer ) laterPointer += bufferSize; if ( pointer < earlierPointer ) pointer += bufferSize; return pointer >= earlierPointer && pointer < laterPointer; } // A structure to hold various information related to the DirectSound // API implementation. struct DsHandle { unsigned int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. void *id[2]; void *buffer[2]; bool xrun[2]; UINT bufferPointer[2]; DWORD dsBufferSize[2]; DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. HANDLE condition; DsHandle() :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } }; // Declarations for utility functions, callbacks, and structures // specific to the DirectSound implementation. static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR module, LPVOID lpContext ); static const char* getErrorString( int code ); static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { LPGUID id[2]; bool validId[2]; bool found; std::string name; DsDevice() : found(false) { validId[0] = false; validId[1] = false; } }; struct DsProbeData { bool isInput; std::vector* dsDevices; }; RtApiDs :: RtApiDs() { // Dsound will run both-threaded. If CoInitialize fails, then just // accept whatever the mainline chose for a threading model. coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; } RtApiDs :: ~RtApiDs() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); // balanced call. } // The DirectSound default output is always the first device. unsigned int RtApiDs :: getDefaultOutputDevice( void ) { return 0; } // The DirectSound default input is always the first input device, // which is the first capture device enumerated. unsigned int RtApiDs :: getDefaultInputDevice( void ) { return 0; } unsigned int RtApiDs :: getDeviceCount( void ) { // Set query flag for previously found devices to false, so that we // can check for any devices that have disappeared. for ( unsigned int i=0; i(dsDevices.size()); } RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; if ( dsDevices.size() == 0 ) { // Force a query of all devices getDeviceCount(); if ( dsDevices.size() == 0 ) { errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } } if ( device >= dsDevices.size() ) { errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } HRESULT result; if ( dsDevices[ device ].validId[0] == false ) goto probeInput; LPDIRECTSOUND output; DSCAPS outCaps; result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto probeInput; } outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto probeInput; } // Get output channel information. info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; // Get sample rate information. info.sampleRates.clear(); for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } // Get format information. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; output->Release(); if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( dsDevices[ device ].validId[1] == false ) { info.name = dsDevices[ device ].name; info.probed = true; return info; } probeInput: LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get input channel information. info.inputChannels = inCaps.dwChannels; // Get sample rate and format information. std::vector rates; if ( inCaps.dwChannels >= 2 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); } } else if ( inCaps.dwChannels == 1 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); } } else info.inputChannels = 0; // technically, this would be an error input->Release(); if ( info.inputChannels == 0 ) return info; // Copy the supported rates to the info structure but avoid duplication. bool found; for ( unsigned int i=0; i 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; if ( device == 0 ) info.isDefaultInput = true; // Copy name and return. info.name = dsDevices[ device ].name; info.probed = true; return info; } bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { if ( channels + firstChannel > 2 ) { errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; return FAILURE; } size_t nDevices = dsDevices.size(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!"; return FAILURE; } if ( mode == OUTPUT ) { if ( dsDevices[ device ].validId[0] == false ) { errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!"; errorText_ = errorStream_.str(); return FAILURE; } } else { // mode == INPUT if ( dsDevices[ device ].validId[1] == false ) { errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!"; errorText_ = errorStream_.str(); return FAILURE; } } // According to a note in PortAudio, using GetDesktopWindow() // instead of GetForegroundWindow() is supposed to avoid problems // that occur when the application's window is not the foreground // window. Also, if the application window closes before the // DirectSound buffer, DirectSound can crash. In the past, I had // problems when using GetDesktopWindow() but it seems fine now // (January 2010). I'll leave it commented here. // HWND hWnd = GetForegroundWindow(); HWND hWnd = GetDesktopWindow(); // Check the numberOfBuffers parameter and limit the lowest value to // two. This is a judgement call and a value of two is probably too // low for capture, but it should work for playback. int nBuffers = 0; if ( options ) nBuffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; if ( nBuffers < 2 ) nBuffers = 3; // Check the lower range of the user-specified buffer size and set // (arbitrarily) to a lower bound of 32. if ( *bufferSize < 32 ) *bufferSize = 32; // Create the wave format structure. The data format setting will // be determined later. WAVEFORMATEX waveFormat; ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nChannels = channels + firstChannel; waveFormat.nSamplesPerSec = (unsigned long) sampleRate; // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; HRESULT result; if ( mode == OUTPUT ) { LPDIRECTSOUND output; result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCAPS outCaps; outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; errorText_ = errorStream_.str(); return FAILURE; } // Check format information. Use 16-bit format unless not // supported or user requests 8-bit. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Even though we will write to the secondary buffer, we need to // access the primary buffer to set the correct output format // (since the default is 8-bit, 22 kHz!). Setup the DS primary // buffer description. DSBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; // Obtain the primary buffer LPDIRECTSOUNDBUFFER buffer; result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Set the primary DS buffer sound format. result = buffer->SetFormat( &waveFormat ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Setup the secondary DS buffer description. ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCHARDWARE ); // Force hardware mixing bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Try to create the secondary DS buffer. If that doesn't work, // try to use software mixing. Otherwise, there's a problem. result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE ); // Force software mixing result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } } // Get the buffer size ... might be different from what we specified. DSBCAPS dsbcaps; dsbcaps.dwSize = sizeof( DSBCAPS ); result = buffer->GetCaps( &dsbcaps ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dsbcaps.dwBufferBytes; // Lock the DS buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) output; bhandle = (void *) buffer; } if ( mode == INPUT ) { LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( inCaps.dwChannels < channels + firstChannel ) { errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; return FAILURE; } // Check format information. Use 16-bit format unless user // requests 8-bit. DWORD deviceFormats; if ( channels + firstChannel == 2 ) { deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } else { // channel == 1 deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Setup the secondary DS buffer description. DSCBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); bufferDescription.dwFlags = 0; bufferDescription.dwReserved = 0; bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Create the capture buffer. LPDIRECTSOUNDCAPTUREBUFFER buffer; result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Get the buffer size ... might be different from what we specified. DSCBCAPS dscbcaps; dscbcaps.dwSize = sizeof( DSCBCAPS ); result = buffer->GetCaps( &dscbcaps ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dscbcaps.dwBufferBytes; // NOTE: We could have a problem here if this is a duplex stream // and the play and capture hardware buffer sizes are different // (I'm actually not sure if that is a problem or not). // Currently, we are not verifying that. // Lock the capture buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the buffer ZeroMemory( audioPtr, dataLen ); // Unlock the buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) input; bhandle = (void *) buffer; } // Set various stream parameters DsHandle *handle = 0; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.nUserChannels[mode] = channels; stream_.bufferSize = *bufferSize; stream_.channelOffset[mode] = firstChannel; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Set flag for buffer conversion stream_.doConvertBuffer[mode] = false; if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) stream_.doConvertBuffer[mode] = true; if (stream_.userFormat != stream_.deviceFormat[mode]) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate our DsHandle structures for the stream. if ( stream_.apiHandle == 0 ) { try { handle = new DsHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } else handle = (DsHandle *) stream_.apiHandle; handle->id[mode] = ohandle; handle->buffer[mode] = bhandle; handle->dsBufferSize[mode] = dsBufferSize; handle->dsPointerLeadTime[mode] = dsPointerLeadTime; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up an output stream. stream_.mode = DUPLEX; else stream_.mode = mode; stream_.nBuffers = nBuffers; stream_.sampleRate = sampleRate; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup the callback thread. if ( stream_.callbackInfo.isRunning == false ) { unsigned threadId; stream_.callbackInfo.isRunning = true; stream_.callbackInfo.object = (void *) this; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, &stream_.callbackInfo, 0, &threadId ); if ( stream_.callbackInfo.thread == 0 ) { errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; goto error; } // Boost DS thread priority SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); } return SUCCESS; error: if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) buffer->Release(); object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) buffer->Release(); object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } // Stop the callback thread. stream_.callbackInfo.isRunning = false; WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); CloseHandle( (HANDLE) stream_.callbackInfo.thread ); DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiDs :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of // increasing timer accuracy). On greater windows (Win2K or later), // this is already in effect. timeBeginPeriod( 1 ); buffersRolling = false; duplexPrerollBytes = 0; if ( stream_.mode == DUPLEX ) { // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); } HRESULT result = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; result = buffer->Start( DSCBSTART_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; unlock: if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } HRESULT result = 0; LPVOID audioPtr; DWORD dataLen; DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); // Stop the buffer and clear memory LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start playing again, we must begin at beginning of buffer. handle->bufferPointer[0] = 0; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; audioPtr = NULL; dataLen = 0; stream_.state = STREAM_STOPPED; if ( stream_.mode != DUPLEX ) MUTEX_LOCK( &stream_.mutex ); result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start recording again, we must begin at beginning of buffer. handle->bufferPointer[1] = 0; } unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. MUTEX_UNLOCK( &stream_.mutex ); if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } DsHandle *handle = (DsHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } void RtApiDs :: callbackEvent() { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { Sleep( 50 ); // sleep 50 milliseconds return; } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; DsHandle *handle = (DsHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > stream_.nBuffers + 2 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else stopStream(); return; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } HRESULT result; DWORD currentWritePointer, safeWritePointer; DWORD currentReadPointer, safeReadPointer; UINT nextWritePointer; LPVOID buffer1 = NULL; LPVOID buffer2 = NULL; DWORD bufferSize1 = 0; DWORD bufferSize2 = 0; char *buffer; long bufferBytes; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } if ( buffersRolling == false ) { if ( stream_.mode == DUPLEX ) { //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); // It takes a while for the devices to get rolling. As a result, // there's no guarantee that the capture and write device pointers // will move in lockstep. Wait here for both devices to start // rolling, and then set our buffer pointers accordingly. // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 // bytes later than the write buffer. // Stub: a serious risk of having a pre-emptive scheduling round // take place between the two GetCurrentPosition calls... but I'm // really not sure how to solve the problem. Temporarily boost to // Realtime priority, maybe; but I'm not sure what priority the // DirectSound service threads run at. We *should* be roughly // within a ms or so of correct. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; DWORD startSafeWritePointer, startSafeReadPointer; result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } while ( true ) { result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; Sleep( 1 ); } //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; handle->bufferPointer[1] = safeReadPointer; } else if ( stream_.mode == OUTPUT ) { // Set the proper nextWritePosition after initial startup. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; } buffersRolling = true; } if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( handle->drainCounter > 1 ) { // write zeros to the output stream bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); memset( stream_.userBuffer[0], 0, bufferBytes ); } // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; bufferBytes *= formatBytes( stream_.deviceFormat[0] ); } else { buffer = stream_.userBuffer[0]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); } // No byte swapping necessary in DirectSound implementation. // Ahhh ... windoze. 16-bit data is signed but 8-bit data is // unsigned. So, we need to convert our signed 8-bit data here to // unsigned. if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) for ( int i=0; idsBufferSize[0]; nextWritePointer = handle->bufferPointer[0]; DWORD endWrite, leadPointer; while ( true ) { // Find out where the read and "safe write" pointers are. result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } // We will copy our output buffer into the region between // safeWritePointer and leadPointer. If leadPointer is not // beyond the next endWrite position, wait until it is. leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset endWrite = nextWritePointer + bufferBytes; // Check whether the entire write region is behind the play pointer. if ( leadPointer >= endWrite ) break; // If we are here, then we must wait until the leadPointer advances // beyond the end of our next write region. We use the // Sleep() function to suspend operation until that happens. double millis = ( endWrite - leadPointer ) * 1000.0; millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); } if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { // We've strayed into the forbidden zone ... resync the read pointer. handle->xrun[0] = true; nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; handle->bufferPointer[0] = nextWritePointer; endWrite = nextWritePointer + bufferBytes; } // Lock free space in the buffer result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } // Copy our buffer into the DS buffer CopyMemory( buffer1, buffer, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); // Update our buffer offset and unlock sound buffer dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; handle->bufferPointer[0] = nextWritePointer; } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; bufferBytes *= formatBytes( stream_.deviceFormat[1] ); } else { buffer = stream_.userBuffer[1]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; bufferBytes *= formatBytes( stream_.userFormat ); } LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; long nextReadPointer = handle->bufferPointer[1]; DWORD dsBufferSize = handle->dsBufferSize[1]; // Find out where the write and "safe read" pointers are. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset DWORD endRead = nextReadPointer + bufferBytes; // Handling depends on whether we are INPUT or DUPLEX. // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, // then a wait here will drag the write pointers into the forbidden zone. // // In DUPLEX mode, rather than wait, we will back off the read pointer until // it's in a safe position. This causes dropouts, but it seems to be the only // practical way to sync up the read and write pointers reliably, given the // the very complex relationship between phase and increment of the read and write // pointers. // // In order to minimize audible dropouts in DUPLEX mode, we will // provide a pre-roll period of 0.5 seconds in which we return // zeros from the read buffer while the pointers sync up. if ( stream_.mode == DUPLEX ) { if ( safeReadPointer < endRead ) { if ( duplexPrerollBytes <= 0 ) { // Pre-roll time over. Be more aggressive. int adjustment = endRead-safeReadPointer; handle->xrun[1] = true; // Two cases: // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, // and perform fine adjustments later. // - small adjustments: back off by twice as much. if ( adjustment >= 2*bufferBytes ) nextReadPointer = safeReadPointer-2*bufferBytes; else nextReadPointer = safeReadPointer-bufferBytes-adjustment; if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } else { // In pre=roll time. Just do it. nextReadPointer = safeReadPointer - bufferBytes; while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } endRead = nextReadPointer + bufferBytes; } } else { // mode == INPUT while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { // See comments for playback. double millis = (endRead - safeReadPointer) * 1000.0; millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); // Wake up and find out where we are now. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset } } // Lock free space in the buffer result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( duplexPrerollBytes <= 0 ) { // Copy our buffer into the DS buffer CopyMemory( buffer, buffer1, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); } else { memset( buffer, 0, bufferSize1 ); if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); duplexPrerollBytes -= bufferSize1 + bufferSize2; } // Update our buffer offset and unlock sound buffer nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } handle->bufferPointer[1] = nextReadPointer; // No byte swapping necessary in DirectSound implementation. // If necessary, convert 8-bit data from unsigned to signed. if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) for ( int j=0; jobject; bool* isRunning = &info->isRunning; while ( *isRunning == true ) { object->callbackEvent(); } _endthreadex( 0 ); return 0; } static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR /*module*/, LPVOID lpContext ) { struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; if ( probeInfo.isInput == true ) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) validDevice = true; } object->Release(); } else { DSCAPS caps; LPDIRECTSOUND object; hr = DirectSoundCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) validDevice = true; } object->Release(); } // If good device, then save its name and guid. std::string name = convertCharPointerToStdString( description ); if ( validDevice ) { for ( unsigned int i=0; i #include // A structure to hold various information related to the ALSA API // implementation. struct AlsaHandle { snd_pcm_t *handles[2]; bool synchronized; bool xrun[2]; pthread_cond_t runnable_cv; bool runnable; AlsaHandle() #if _cplusplus >= 201103L :handles{nullptr, nullptr}, synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } #else : synchronized(false), runnable(false) { handles[0] = NULL; handles[1] = NULL; xrun[0] = false; xrun[1] = false; } #endif }; static void *alsaCallbackHandler( void * ptr ); RtApiAlsa :: RtApiAlsa() { // Nothing to do here. } RtApiAlsa :: ~RtApiAlsa() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiAlsa :: getDeviceCount( void ) { unsigned nDevices = 0; int result, subdevice, card; char name[64]; snd_ctl_t *handle = 0; strcpy(name, "default"); result = snd_ctl_open( &handle, "default", 0 ); if (result == 0) { nDevices++; snd_ctl_close( handle ); } // Count cards and devices card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { handle = 0; errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto nextcard; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( handle, &subdevice ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); break; } if ( subdevice < 0 ) break; nDevices++; } nextcard: if ( handle ) snd_ctl_close( handle ); snd_card_next( &card ); } return nDevices; } RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; unsigned nDevices = 0; int result=-1, subdevice=-1, card=-1; char name[64]; snd_ctl_t *chandle = 0; result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); if ( result == 0 ) { if ( nDevices++ == device ) { strcpy( name, "default" ); goto foundDevice; } } if ( chandle ) snd_ctl_close( chandle ); // Count cards and devices snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { chandle = 0; errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto nextcard; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( chandle, &subdevice ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); break; } if ( subdevice < 0 ) break; if ( nDevices == device ) { sprintf( name, "hw:%d,%d", card, subdevice ); goto foundDevice; } nDevices++; } nextcard: if ( chandle ) snd_ctl_close( chandle ); snd_card_next( &card ); } if ( nDevices == 0 ) { errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } foundDevice: // If a stream is already open, we cannot probe the stream devices. // Thus, use the saved results. if ( stream_.state != STREAM_CLOSED && ( stream_.device[0] == device || stream_.device[1] == device ) ) { snd_ctl_close( chandle ); if ( device >= devices_.size() ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; error( RtAudioError::WARNING ); return info; } return devices_[ device ]; } int openMode = SND_PCM_ASYNC; snd_pcm_stream_t stream; snd_pcm_info_t *pcminfo; snd_pcm_info_alloca( &pcminfo ); snd_pcm_t *phandle; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); // First try for playback unless default device (which has subdev -1) stream = SND_PCM_STREAM_PLAYBACK; snd_pcm_info_set_stream( pcminfo, stream ); if ( subdevice != -1 ) { snd_pcm_info_set_device( pcminfo, subdevice ); snd_pcm_info_set_subdevice( pcminfo, 0 ); result = snd_ctl_pcm_info( chandle, pcminfo ); if ( result < 0 ) { // Device probably doesn't support playback. goto captureProbe; } } result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } // Get output channel information. unsigned int value; result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } info.outputChannels = value; snd_pcm_close( phandle ); captureProbe: stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); // Now try for capture unless default device (with subdev = -1) if ( subdevice != -1 ) { result = snd_ctl_pcm_info( chandle, pcminfo ); snd_ctl_close( chandle ); if ( result < 0 ) { // Device probably doesn't support capture. if ( info.outputChannels == 0 ) return info; goto probeParameters; } } else snd_ctl_close( chandle ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } info.inputChannels = value; snd_pcm_close( phandle ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // ALSA doesn't provide default devices so we'll use the first available one. if ( device == 0 && info.outputChannels > 0 ) info.isDefaultOutput = true; if ( device == 0 && info.inputChannels > 0 ) info.isDefaultInput = true; probeParameters: // At this point, we just need to figure out the supported data // formats and sample rates. We'll proceed by opening the device in // the direction with the maximum number of channels, or playback if // they are equal. This might limit our sample rate options, but so // be it. if ( info.outputChannels >= info.inputChannels ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Test our discrete set of sample rate values. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } if ( info.sampleRates.size() == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe the supported data formats ... we don't care about endian-ness just yet snd_pcm_format_t format; info.nativeFormats = 0; format = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT8; format = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT16; format = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT24; format = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT32; format = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT32; format = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT64; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get the device name if (strncmp(name, "default", 7)!=0) { char *cardname; result = snd_card_get_name( card, &cardname ); if ( result >= 0 ) { sprintf( name, "hw:%s,%d", cardname, subdevice ); free( cardname ); } } info.name = name; // That's all ... close the device and return snd_pcm_close( phandle ); info.probed = true; return info; } void RtApiAlsa :: saveDeviceInfo( void ) { devices_.clear(); unsigned int nDevices = getDeviceCount(); devices_.resize( nDevices ); for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT) ) { strcpy(name, "default"); result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } } else { nDevices++; // Count cards and devices card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( chandle, &subdevice ); if ( result < 0 ) break; if ( subdevice < 0 ) break; if ( nDevices == device ) { sprintf( name, "hw:%d,%d", card, subdevice ); snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } snd_ctl_close( chandle ); snd_card_next( &card ); } if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; return FAILURE; } } foundDevice: // The getDeviceInfo() function will not work for a device that is // already open. Thus, we'll probe the system before opening a // stream and save the results for use by getDeviceInfo(). if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once this->saveDeviceInfo(); snd_pcm_stream_t stream; if ( mode == OUTPUT ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_t *phandle; int openMode = SND_PCM_ASYNC; result = snd_pcm_open( &phandle, name, stream, openMode ); if ( result < 0 ) { if ( mode == OUTPUT ) errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; else errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; errorText_ = errorStream_.str(); return FAILURE; } // Fill the parameter structure. snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca( &hw_params ); result = snd_pcm_hw_params_any( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set access ... check user preference. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { stream_.userInterleaved = false; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); stream_.deviceInterleaved[mode] = true; } else stream_.deviceInterleaved[mode] = false; } else { stream_.userInterleaved = true; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); stream_.deviceInterleaved[mode] = false; } else stream_.deviceInterleaved[mode] = true; } if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; if ( format == RTAUDIO_SINT8 ) deviceFormat = SND_PCM_FORMAT_S8; else if ( format == RTAUDIO_SINT16 ) deviceFormat = SND_PCM_FORMAT_S16; else if ( format == RTAUDIO_SINT24 ) deviceFormat = SND_PCM_FORMAT_S24; else if ( format == RTAUDIO_SINT32 ) deviceFormat = SND_PCM_FORMAT_S32; else if ( format == RTAUDIO_FLOAT32 ) deviceFormat = SND_PCM_FORMAT_FLOAT; else if ( format == RTAUDIO_FLOAT64 ) deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { stream_.deviceFormat[mode] = format; goto setFormat; } // The user requested format is not natively supported by the device. deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; goto setFormat; } deviceFormat = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT8; goto setFormat; } // If we get here, no supported format was found. snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; setFormat: result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine whether byte-swaping is necessary. stream_.doByteSwap[mode] = false; if ( deviceFormat != SND_PCM_FORMAT_S8 ) { result = snd_pcm_format_cpu_endian( deviceFormat ); if ( result == 0 ) stream_.doByteSwap[mode] = true; else if (result < 0) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } } // Set the sample rate. result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine the number of channels for this device. We support a possible // minimum device channel number > than the value requested by the user. stream_.nUserChannels[mode] = channels; unsigned int value; result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); unsigned int deviceChannels = value; if ( result < 0 || deviceChannels < channels + firstChannel ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } deviceChannels = value; if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; stream_.nDeviceChannels[mode] = deviceChannels; // Set the device channels. result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Set the buffer (or period) size. int dir = 0; snd_pcm_uframes_t periodSize = *bufferSize; result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } *bufferSize = periodSize; // Set the buffer number, which in ALSA is referred to as the "period". unsigned int periods = 0; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; if ( periods < 2 ) periods = 4; // a fairly safe default value result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; // Install the hardware configuration result = snd_pcm_hw_params( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. snd_pcm_sw_params_t *sw_params = NULL; snd_pcm_sw_params_alloca( &sw_params ); snd_pcm_sw_params_current( phandle, sw_params ); snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); // The following two settings were suggested by Theo Veenker //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); // here are two options for a fix //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); snd_pcm_uframes_t val; snd_pcm_sw_params_get_boundary( sw_params, &val ); snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); result = snd_pcm_sw_params( phandle, sw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); snd_pcm_sw_params_dump( sw_params, out ); #endif // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the ApiHandle if necessary and then save. AlsaHandle *apiInfo = 0; if ( stream_.apiHandle == 0 ) { try { apiInfo = (AlsaHandle *) new AlsaHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; goto error; } if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) apiInfo; apiInfo->handles[0] = 0; apiInfo->handles[1] = 0; } else { apiInfo = (AlsaHandle *) stream_.apiHandle; } apiInfo->handles[mode] = phandle; phandle = 0; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.nBuffers = periods; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; // Link the streams if possible. apiInfo->synchronized = false; if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) apiInfo->synchronized = true; else { errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; error( RtAudioError::WARNING ); } } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { // Failed. Try instead with default attributes. result = pthread_create( &stream_.callbackInfo.thread, NULL, alsaCallbackHandler, &stream_.callbackInfo ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiAlsa::error creating callback thread!"; goto error; } } } return SUCCESS; error: if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } if ( phandle) snd_pcm_close( phandle ); for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[0] ); if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[1] ); } if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiAlsa :: startStream() { // This method calls snd_pcm_prepare if the device isn't already in that state. verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { state = snd_pcm_state( handle[0] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open state = snd_pcm_state( handle[1] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } stream_.state = STREAM_RUNNING; unlock: apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( apiInfo->synchronized ) result = snd_pcm_drop( handle[0] ); else result = snd_pcm_drain( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = snd_pcm_drop( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: callbackEvent() { AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !apiInfo->runnable ) pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; apiInfo->xrun[0] = false; } if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; apiInfo->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int channels; snd_pcm_t **handle; snd_pcm_sframes_t frames; RtAudioFormat format; handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; channels = stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; channels = stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[1] ) result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[1] = true; result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RtAudioError::WARNING ); goto tryOutput; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); // Check stream latency result = snd_pcm_delay( handle[1], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; } tryOutput: if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); channels = stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; channels = stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer(buffer, stream_.bufferSize * channels, format); // Write samples to device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[0] ) result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[0] = true; result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } else errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun."; } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RtAudioError::WARNING ); goto unlock; } // Check stream latency result = snd_pcm_delay( handle[0], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *alsaCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { std::cerr << "RtAudio alsa: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_ALSA__ *********************// #endif #if defined(__LINUX_PULSE__) // Code written by Peter Meerwald, pmeerw@pmeerw.net // and Tristan Matthews. #include #include #include #include static pa_mainloop_api *rt_pa_mainloop_api = NULL; struct PaDeviceInfo { PaDeviceInfo() : sink_index(-1), source_index(-1) {} int sink_index; int source_index; std::string sink_name; std::string source_name; RtAudio::DeviceInfo info; }; static struct { std::vector dev; std::string default_sink_name; std::string default_source_name; int default_rate; } rt_pa_info; static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 96000, 192000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; pa_sample_format_t pa_format; }; static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, {RTAUDIO_SINT24, PA_SAMPLE_S24LE}, {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; struct PulseAudioHandle { pa_simple *s_play; pa_simple *s_rec; pthread_t thread; pthread_cond_t runnable_cv; bool runnable; PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; static void rt_pa_mainloop_api_quit(int ret) { rt_pa_mainloop_api->quit(rt_pa_mainloop_api, ret); } static void rt_pa_set_server_info(pa_context *context, const pa_server_info *info, void *data){ (void)context; (void)data; pa_sample_spec ss; if (!info) { rt_pa_mainloop_api_quit(1); return; } ss = info->sample_spec; rt_pa_info.default_rate = ss.rate; rt_pa_info.default_sink_name = info->default_sink_name; rt_pa_info.default_source_name = info->default_source_name; } static void rt_pa_set_sink_info(pa_context * /*c*/, const pa_sink_info *i, int eol, void * /*userdata*/) { if (eol) return; PaDeviceInfo inf; inf.info.name = pa_proplist_gets(i->proplist, "device.description"); inf.info.probed = true; inf.info.outputChannels = i->sample_spec.channels; inf.info.preferredSampleRate = i->sample_spec.rate; inf.info.isDefaultOutput = (rt_pa_info.default_sink_name == i->name); inf.sink_index = i->index; inf.sink_name = i->name; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) inf.info.sampleRates.push_back( *sr ); for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; fm->rtaudio_format; ++fm ) inf.info.nativeFormats |= fm->rtaudio_format; for (size_t i=0; i < rt_pa_info.dev.size(); i++) { /* Attempt to match up sink and source records by device description. */ if (rt_pa_info.dev[i].info.name == inf.info.name) { rt_pa_info.dev[i].sink_index = inf.sink_index; rt_pa_info.dev[i].sink_name = inf.sink_name; rt_pa_info.dev[i].info.outputChannels = inf.info.outputChannels; rt_pa_info.dev[i].info.isDefaultOutput = inf.info.isDefaultOutput; /* Assume duplex channels are minimum of input and output channels. */ /* Uncomment if we add support for DUPLEX if (rt_pa_info.dev[i].source_index > -1) (inf.info.outputChannels < rt_pa_info.dev[i].info.inputChannels) ? inf.info.outputChannels : rt_pa_info.dev[i].info.inputChannels; */ return; } } /* try to ensure device #0 is the default */ if (inf.info.isDefaultOutput) rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); else rt_pa_info.dev.push_back(inf); } static void rt_pa_set_source_info_and_quit(pa_context * /*c*/, const pa_source_info *i, int eol, void * /*userdata*/) { if (eol) { rt_pa_mainloop_api_quit(0); return; } PaDeviceInfo inf; inf.info.name = pa_proplist_gets(i->proplist, "device.description"); inf.info.probed = true; inf.info.inputChannels = i->sample_spec.channels; inf.info.preferredSampleRate = i->sample_spec.rate; inf.info.isDefaultInput = (rt_pa_info.default_source_name == i->name); inf.source_index = i->index; inf.source_name = i->name; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) inf.info.sampleRates.push_back( *sr ); for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; fm->rtaudio_format; ++fm ) inf.info.nativeFormats |= fm->rtaudio_format; for (size_t i=0; i < rt_pa_info.dev.size(); i++) { /* Attempt to match up sink and source records by device description. */ if (rt_pa_info.dev[i].info.name == inf.info.name) { rt_pa_info.dev[i].source_index = inf.source_index; rt_pa_info.dev[i].source_name = inf.source_name; rt_pa_info.dev[i].info.inputChannels = inf.info.inputChannels; rt_pa_info.dev[i].info.isDefaultInput = inf.info.isDefaultInput; /* Assume duplex channels are minimum of input and output channels. */ /* Uncomment if we add support for DUPLEX if (rt_pa_info.dev[i].sink_index > -1) { rt_pa_info.dev[i].info.duplexChannels = (inf.info.inputChannels < rt_pa_info.dev[i].info.outputChannels) ? inf.info.inputChannels : rt_pa_info.dev[i].info.outputChannels; } */ return; } } /* try to ensure device #0 is the default */ if (inf.info.isDefaultInput) rt_pa_info.dev.insert(rt_pa_info.dev.begin(), inf); else rt_pa_info.dev.push_back(inf); } static void rt_pa_context_state_callback(pa_context *context, void *userdata) { (void)userdata; auto state = pa_context_get_state(context); switch (state) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: rt_pa_info.dev.clear(); pa_context_get_server_info(context, rt_pa_set_server_info, NULL); pa_context_get_sink_info_list(context, rt_pa_set_sink_info, NULL); pa_context_get_source_info_list(context, rt_pa_set_source_info_and_quit, NULL); break; case PA_CONTEXT_TERMINATED: rt_pa_mainloop_api_quit(0); break; case PA_CONTEXT_FAILED: default: rt_pa_mainloop_api_quit(1); } } RtApiPulse::~RtApiPulse() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } void RtApiPulse::collectDeviceInfo( void ) { pa_context *context = NULL; pa_mainloop *m = NULL; char *server = NULL; int ret = 1; if (!(m = pa_mainloop_new())) { errorStream_ << "RtApiPulse::DeviceInfo pa_mainloop_new() failed."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto quit; } rt_pa_mainloop_api = pa_mainloop_get_api(m); if (!(context = pa_context_new_with_proplist(rt_pa_mainloop_api, NULL, NULL))) { errorStream_ << "pa_context_new() failed."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto quit; } pa_context_set_state_callback(context, rt_pa_context_state_callback, NULL); if (pa_context_connect(context, server, PA_CONTEXT_NOFLAGS, NULL) < 0) { errorStream_ << "RtApiPulse::DeviceInfo pa_context_connect() failed: " << pa_strerror(pa_context_errno(context)); errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto quit; } if (pa_mainloop_run(m, &ret) < 0) { errorStream_ << "pa_mainloop_run() failed."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto quit; } if (ret != 0) { errorStream_ << "could not get server info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto quit; } quit: if (context) pa_context_unref(context); if (m) { pa_mainloop_free(m); } pa_xfree(server); } unsigned int RtApiPulse::getDeviceCount( void ) { collectDeviceInfo(); return rt_pa_info.dev.size(); } RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) { if (rt_pa_info.dev.size()==0) collectDeviceInfo(); if (device < rt_pa_info.dev.size()) return rt_pa_info.dev[device].info; return RtAudio::DeviceInfo(); } static void *pulseaudio_callback( void * user ) { CallbackInfo *cbi = static_cast( user ); RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (cbi->doRealtime) { std::cerr << "RtAudio pulse: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning ) { pthread_testcancel(); context->callbackEvent(); } pthread_exit( NULL ); } void RtApiPulse::closeStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); stream_.callbackInfo.isRunning = false; if ( pah ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( pah->thread, 0 ); if ( pah->s_play ) { pa_simple_flush( pah->s_play, NULL ); pa_simple_free( pah->s_play ); } if ( pah->s_rec ) pa_simple_free( pah->s_rec ); pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } if ( stream_.userBuffer[0] ) { free( stream_.userBuffer[0] ); stream_.userBuffer[0] = 0; } if ( stream_.userBuffer[1] ) { free( stream_.userBuffer[1] ); stream_.userBuffer[1] = 0; } stream_.state = STREAM_CLOSED; stream_.mode = UNINITIALIZED; } void RtApiPulse::callbackEvent( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !pah->runnable ) pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " "this shouldn't happen!"; error( RtAudioError::WARNING ); return; } RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; if ( stream_.state != STREAM_RUNNING ) goto unlock; int pa_error; size_t bytes; if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[OUTPUT] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[OUTPUT] ); } else bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { if ( stream_.doConvertBuffer[INPUT] ) bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[INPUT] ); else bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } if ( stream_.doConvertBuffer[INPUT] ) { convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) stopStream(); } void RtApiPulse::startStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::startStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiPulse::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif stream_.state = STREAM_RUNNING; pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); } void RtApiPulse::stopStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah ) { pah->runnable = false; if ( pah->s_play ) { int pa_error; if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::stopStream: error draining output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); } void RtApiPulse::abortStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah ) { pah->runnable = false; if ( pah->s_play ) { int pa_error; if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); } bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { PulseAudioHandle *pah = 0; unsigned long bufferBytes = 0; pa_sample_spec ss; if ( device >= rt_pa_info.dev.size() ) return false; if ( firstChannel != 0 ) { errorText_ = "PulseAudio does not support channel offset mapping."; return false; } /* these may be NULL for default, but we've already got the names */ const char *dev_input = NULL; const char *dev_output = NULL; if (!rt_pa_info.dev[device].source_name.empty()) dev_input = rt_pa_info.dev[device].source_name.c_str(); if (!rt_pa_info.dev[device].sink_name.empty()) dev_output = rt_pa_info.dev[device].sink_name.c_str(); if (mode==INPUT && rt_pa_info.dev[device].info.inputChannels == 0) { errorText_ = "PulseAudio device does not support input."; return false; } if (mode==OUTPUT && rt_pa_info.dev[device].info.outputChannels == 0) { errorText_ = "PulseAudio device does not support output."; return false; } if (mode==DUPLEX && rt_pa_info.dev[device].info.duplexChannels == 0) { /* Note: will always error, DUPLEX not yet supported */ errorText_ = "PulseAudio device does not support duplex."; return false; } if (mode==INPUT && rt_pa_info.dev[device].info.inputChannels < channels) { errorText_ = "PulseAudio: unsupported number of input channels."; return false; } if (mode==OUTPUT && rt_pa_info.dev[device].info.outputChannels < channels) { errorText_ = "PulseAudio: unsupported number of output channels."; return false; } if (mode==DUPLEX && rt_pa_info.dev[device].info.duplexChannels < channels) { /* Note: will always error, DUPLEX not yet supported */ errorText_ = "PulseAudio: unsupported number of duplex channels."; return false; } ss.channels = channels; bool sr_found = false; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { if ( sampleRate == *sr ) { sr_found = true; stream_.sampleRate = sampleRate; ss.rate = sampleRate; break; } } if ( !sr_found ) { stream_.sampleRate = sampleRate; ss.rate = sampleRate; } bool sf_found = 0; for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { if ( format == sf->rtaudio_format ) { sf_found = true; stream_.userFormat = sf->rtaudio_format; stream_.deviceFormat[mode] = stream_.userFormat; ss.format = sf->pa_format; break; } } if ( !sf_found ) { // Use internal data format conversion. stream_.userFormat = format; stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; ss.format = PA_SAMPLE_FLOAT32LE; } // Set other stream parameters. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; stream_.nBuffers = options ? options->numberOfBuffers : 1; stream_.doByteSwap[mode] = false; stream_.nUserChannels[mode] = channels; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.channelOffset[mode] = 0; std::string streamName = "RtAudio"; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; goto error; } stream_.bufferSize = *bufferSize; if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.device[mode] = device; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); if ( !stream_.apiHandle ) { PulseAudioHandle *pah = new PulseAudioHandle; if ( !pah ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; goto error; } stream_.apiHandle = pah; if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; goto error; } } pah = static_cast( stream_.apiHandle ); int error; if ( options && !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { pa_buffer_attr buffer_attr; case INPUT: buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, dev_input, "Record", &ss, NULL, &buffer_attr, &error ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; case OUTPUT: { pa_buffer_attr * attr_ptr; if ( options && options->numberOfBuffers > 0 ) { // pa_buffer_attr::fragsize is recording-only. // Hopefully PortAudio won't access uninitialized fields. buffer_attr.maxlength = bufferBytes * options->numberOfBuffers; buffer_attr.minreq = -1; buffer_attr.prebuf = -1; buffer_attr.tlength = -1; attr_ptr = &buffer_attr; } else { attr_ptr = nullptr; } pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, dev_output, "Playback", &ss, NULL, attr_ptr, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } break; } case DUPLEX: /* Note: We could add DUPLEX by synchronizing multiple streams, but it would mean moving from Simple API to Asynchronous API: https://freedesktop.org/software/pulseaudio/doxygen/streams.html#sync_streams */ errorText_ = "RtApiPulse::probeDeviceOpen: duplex not supported for PulseAudio."; goto error; default: goto error; } if ( stream_.mode == UNINITIALIZED ) stream_.mode = mode; else if ( stream_.mode == mode ) goto error; else stream_.mode = DUPLEX; if ( !stream_.callbackInfo.isRunning ) { stream_.callbackInfo.object = this; stream_.state = STREAM_STOPPED; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; int result = pthread_create( &pah->thread, &attr, pulseaudio_callback, (void *)&stream_.callbackInfo); pthread_attr_destroy(&attr); if(result != 0) { // Failed. Try instead with default attributes. result = pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo); if(result != 0) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; goto error; } } } return SUCCESS; error: if ( pah && stream_.callbackInfo.isRunning ) { pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } //******************** End of __LINUX_PULSE__ *********************// #endif #if defined(__LINUX_OSS__) #include #include #include #include #include #include #include static void *ossCallbackHandler(void * ptr); // A structure to hold various information related to the OSS API // implementation. struct OssHandle { int id[2]; // device ids bool xrun[2]; bool triggered; pthread_cond_t runnable; OssHandle() :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiOss :: RtApiOss() { // Nothing to do here. } RtApiOss :: ~RtApiOss() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiOss :: getDeviceCount( void ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; error( RtAudioError::WARNING ); return 0; } oss_sysinfo sysinfo; if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; error( RtAudioError::WARNING ); return 0; } close( mixerfd ); return sysinfo.numaudios; } RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; error( RtAudioError::WARNING ); return info; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; error( RtAudioError::WARNING ); return info; } unsigned nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } oss_audioinfo ainfo; ainfo.dev = device; result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); close( mixerfd ); if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe channels if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels; if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels; if ( ainfo.caps & PCM_CAP_DUPLEX ) { if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; } // Probe data formats ... do for input unsigned long mask = ainfo.iformats; if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) info.nativeFormats |= RTAUDIO_SINT16; if ( mask & AFMT_S8 ) info.nativeFormats |= RTAUDIO_SINT8; if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) info.nativeFormats |= RTAUDIO_SINT32; #ifdef AFMT_FLOAT if ( mask & AFMT_FLOAT ) info.nativeFormats |= RTAUDIO_FLOAT32; #endif if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) info.nativeFormats |= RTAUDIO_SINT24; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe the supported sample rates. info.sampleRates.clear(); if ( ainfo.nrates ) { for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; break; } } } } else { // Check min and max rate values; for ( unsigned int k=0; k= (int) SAMPLE_RATES[k] ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } else { info.probed = true; info.name = ainfo.name; } return info; } bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; return FAILURE; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; return FAILURE; } unsigned nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!"; return FAILURE; } oss_audioinfo ainfo; ainfo.dev = device; result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); close( mixerfd ); if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); return FAILURE; } // Check if device supports input or output if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) || ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) { if ( mode == OUTPUT ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output."; else errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input."; errorText_ = errorStream_.str(); return FAILURE; } int flags = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( mode == OUTPUT ) flags |= O_WRONLY; else { // mode == INPUT if (stream_.mode == OUTPUT && stream_.device[0] == device) { // We just set the same device for playback ... close and reopen for duplex (OSS only). close( handle->id[0] ); handle->id[0] = 0; if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; errorText_ = errorStream_.str(); return FAILURE; } // Check that the number previously set channels is the same. if ( stream_.nUserChannels[0] != channels ) { errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } flags |= O_RDWR; } else flags |= O_RDONLY; } // Set exclusive access if specified. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; // Try to open the device. int fd; fd = open( ainfo.devnode, flags, 0 ); if ( fd == -1 ) { if ( errno == EBUSY ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; else errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // For duplex operation, specifically set this mode (this doesn't seem to work). /* if ( flags | O_RDWR ) { result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); if ( result == -1) { errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } } */ // Check the device channel support. stream_.nUserChannels[mode] = channels; if ( ainfo.max_channels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; errorText_ = errorStream_.str(); return FAILURE; } // Set the number of channels. int deviceChannels = channels + firstChannel; result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nDeviceChannels[mode] = deviceChannels; // Get the data format mask int mask; result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; int deviceFormat = -1; stream_.doByteSwap[mode] = false; if ( format == RTAUDIO_SINT8 ) { if ( mask & AFMT_S8 ) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } else if ( format == RTAUDIO_SINT16 ) { if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT24 ) { if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT32 ) { if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } } if ( deviceFormat == -1 ) { // The user requested format is not natively supported by the device. if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S8) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } if ( stream_.deviceFormat[mode] == 0 ) { // This really shouldn't happen ... close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; } // Set the data format. int temp = deviceFormat; result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); if ( result == -1 || deviceFormat != temp ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Attempt to set the buffer size. According to OSS, the minimum // number of buffers is two. The supposed minimum buffer size is 16 // bytes, so that will be our lower bound. The argument to this // call is in the form 0xMMMMSSSS (hex), where the buffer size (in // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. // We'll check the actual value used near the end of the setup // procedure. int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; if ( ossBufferBytes < 16 ) ossBufferBytes = 16; int buffers = 0; if ( options ) buffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; if ( buffers < 2 ) buffers = 3; temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nBuffers = buffers; // Save buffer size (in sample frames). *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); stream_.bufferSize = *bufferSize; // Set the sample rate. int srate = sampleRate; result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Verify the sample rate setup worked. if ( abs( srate - (int)sampleRate ) > 100 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = sampleRate; if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { // We're doing duplex setup here. stream_.deviceFormat[0] = stream_.deviceFormat[1]; stream_.nDeviceChannels[0] = deviceChannels; } // Set interleaving parameters. stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the stream handles if necessary and then save. if ( stream_.apiHandle == 0 ) { try { handle = new OssHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; goto error; } if ( pthread_cond_init( &handle->runnable, NULL ) ) { errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else { handle = (OssHandle *) stream_.apiHandle; } handle->id[mode] = fd; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.device[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; if ( stream_.device[0] == device ) handle->id[0] = fd; } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority. The higher priority will only take affect if the // program is run as root or suid. pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { // Failed. Try instead with default attributes. result = pthread_create( &stream_.callbackInfo.thread, NULL, ossCallbackHandler, &stream_.callbackInfo ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiOss::error creating callback thread!"; goto error; } } } return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } OssHandle *handle = (OssHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) pthread_cond_signal( &handle->runnable ); MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); else ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); stream_.state = STREAM_STOPPED; } if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiOss :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiOss::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts // when fed samples. MUTEX_UNLOCK( &stream_.mutex ); OssHandle *handle = (OssHandle *) stream_.apiHandle; pthread_cond_signal( &handle->runnable ); } void RtApiOss :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Flush the output with zeros a few times. char *buffer; int samples; RtAudioFormat format; if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } memset( buffer, 0, samples * formatBytes(format) ); for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; error( RtAudioError::WARNING ); } } result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() { OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); pthread_cond_wait( &handle->runnable, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } // Invoke user callback to get fresh output data. int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { this->abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int samples; RtAudioFormat format; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer( buffer, samples, format ); if ( stream_.mode == DUPLEX && handle->triggered == false ) { int trig = 0; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); result = write( handle->id[0], buffer, samples * formatBytes(format) ); trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); handle->triggered = true; } else // Write samples to device. result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an underrun, though there isn't a // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; error( RtAudioError::WARNING ); // Continue on to input section. } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; samples = stream_.bufferSize * stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device. result = read( handle->id[1], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an overrun, though there isn't a // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; error( RtAudioError::WARNING ); goto unlock; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, samples, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *ossCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (info->doRealtime) { std::cerr << "RtAudio oss: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_OSS__ *********************// #endif // *************************************************** // // // Protected common (OS-independent) RtAudio methods. // // *************************************************** // // This method can be modified to control the behavior of error // message printing. void RtApi :: error( RtAudioError::Type type ) { errorStream_.str(""); // clear the ostringstream RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; if ( errorCallback ) { // abortStream() can generate new error messages. Ignore them. Just keep original one. if ( firstErrorOccurred_ ) return; firstErrorOccurred_ = true; const std::string errorMessage = errorText_; if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { stream_.callbackInfo.isRunning = false; // exit from the thread abortStream(); } errorCallback( type, errorMessage ); firstErrorOccurred_ = false; return; } if ( type == RtAudioError::WARNING && showWarnings_ == true ) std::cerr << '\n' << errorText_ << "\n\n"; else if ( type != RtAudioError::WARNING ) throw( RtAudioError( errorText_, type ) ); } void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApi:: a stream is not open!"; error( RtAudioError::INVALID_USE ); } } void RtApi :: clearStreamInfo() { stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; stream_.sampleRate = 0; stream_.bufferSize = 0; stream_.nBuffers = 0; stream_.userFormat = 0; stream_.userInterleaved = true; stream_.streamTime = 0.0; stream_.apiHandle = 0; stream_.deviceBuffer = 0; stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; stream_.callbackInfo.errorCallback = 0; for ( int i=0; i<2; i++ ) { stream_.device[i] = 11111; stream_.doConvertBuffer[i] = false; stream_.deviceInterleaved[i] = true; stream_.doByteSwap[i] = false; stream_.nUserChannels[i] = 0; stream_.nDeviceChannels[i] = 0; stream_.channelOffset[i] = 0; stream_.deviceFormat[i] = 0; stream_.latency[i] = 0; stream_.userBuffer[i] = 0; stream_.convertInfo[i].channels = 0; stream_.convertInfo[i].inJump = 0; stream_.convertInfo[i].outJump = 0; stream_.convertInfo[i].inFormat = 0; stream_.convertInfo[i].outFormat = 0; stream_.convertInfo[i].inOffset.clear(); stream_.convertInfo[i].outOffset.clear(); } } unsigned int RtApi :: formatBytes( RtAudioFormat format ) { if ( format == RTAUDIO_SINT16 ) return 2; else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) return 4; else if ( format == RTAUDIO_FLOAT64 ) return 8; else if ( format == RTAUDIO_SINT24 ) return 3; else if ( format == RTAUDIO_SINT8 ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; error( RtAudioError::WARNING ); return 0; } void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) { if ( mode == INPUT ) { // convert device to user buffer stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; stream_.convertInfo[mode].outFormat = stream_.userFormat; } else { // convert user to device buffer stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; stream_.convertInfo[mode].inFormat = stream_.userFormat; stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; } if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; else stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; // Set up the interleave/deinterleave offsets. if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || ( mode == INPUT && stream_.userInterleaved ) ) { for ( int k=0; k 0 ) { if ( stream_.deviceInterleaved[mode] ) { if ( mode == OUTPUT ) { for ( int k=0; k info.inJump ) memset( outBuffer, 0, stream_.bufferSize * info.outJump * formatBytes( info.outFormat ) ); int j; if (info.outFormat == RTAUDIO_FLOAT64) { Float64 *out = (Float64 *)outBuffer; if (info.inFormat == RTAUDIO_SINT8) { signed char *in = (signed char *)inBuffer; for (unsigned int i=0; i> 8); //out[info.outOffset[j]] >>= 8; } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 16) & 0x0000ffff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8) & 0x00ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT24) { Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 16); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 24) & 0x000000ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i>8) | (x<<8); } //static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } //static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) { char val; char *ptr; ptr = buffer; if ( format == RTAUDIO_SINT16 ) { for ( unsigned int i=0; i= 4 #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) ) #else #define RTAUDIO_DLL_PUBLIC #endif #endif #include #include #include #include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. Support for signed integers and floats. Audio data fed to/from an RtAudio stream is assumed to ALWAYS be in host byte order. The internal routines will automatically take care of any necessary byte-swapping between the host format and the soundcard. Thus, endian-ness is not a concern in the following format definitions. - \e RTAUDIO_SINT8: 8-bit signed integer. - \e RTAUDIO_SINT16: 16-bit signed integer. - \e RTAUDIO_SINT24: 24-bit signed integer. - \e RTAUDIO_SINT32: 32-bit signed integer. - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. */ typedef unsigned long RtAudioFormat; static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. /*! \typedef typedef unsigned long RtAudioStreamFlags; \brief RtAudio stream option flags. The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). - \e RTAUDIO_JACK_DONT_CONNECT: Do not automatically connect ports (JACK only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows DirectSound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. If the RTAUDIO_JACK_DONT_CONNECT flag is set, RtAudio will not attempt to automatically connect the ports of the client to the audio device. */ typedef unsigned int RtAudioStreamFlags; static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). static const RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT = 0x20; // Do not automatically connect ports (JACK only). /*! \typedef typedef unsigned long RtAudioStreamStatus; \brief RtAudio stream status (over- or underflow) flags. Notification of a stream over- or underflow is indicated by a non-zero stream \c status argument in the RtAudioCallback function. The stream status can be one of the following two options, depending on whether the stream is open for output and/or input: - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. */ typedef unsigned int RtAudioStreamStatus; static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. //! RtAudio callback function prototype. /*! All RtAudio clients must create a function of type RtAudioCallback to read and/or write data from/to the audio stream. When the underlying audio system is ready for new input or output data, this function will be invoked. \param outputBuffer For output (or duplex) streams, the client should write \c nFrames of audio sample frames into this buffer. This argument should be recast to the datatype specified when the stream was opened. For input-only streams, this argument will be NULL. \param inputBuffer For input (or duplex) streams, this buffer will hold \c nFrames of input audio sample frames. This argument should be recast to the datatype specified when the stream was opened. For output-only streams, this argument will be NULL. \param nFrames The number of sample frames of input or output data in the buffers. The actual buffer size in bytes is dependent on the data type and number of channels in use. \param streamTime The number of seconds that have elapsed since the stream was started. \param status If non-zero, this argument indicates a data overflow or underflow condition for the stream. The particular condition can be determined by comparison with the RtAudioStreamStatus flags. \param userData A pointer to optional data provided by the client when opening the stream (default = NULL). \return To continue normal stream operation, the RtAudioCallback function should return a value of zero. To stop the stream and drain the output buffer, the function should return a value of one. To abort the stream immediately, the client should return a value of two. */ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData ); /************************************************************************/ /*! \class RtAudioError \brief Exception handling class for RtAudio. The RtAudioError class is quite simple but it does allow errors to be "caught" by RtAudioError::Type. See the RtAudio documentation to know which methods can throw an RtAudioError. */ /************************************************************************/ class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error { public: //! Defined RtAudioError types. enum Type { WARNING, /*!< A non-critical error. */ DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ UNSPECIFIED, /*!< The default, unspecified error type. */ NO_DEVICES_FOUND, /*!< No devices found on system. */ INVALID_DEVICE, /*!< An invalid device ID was specified. */ MEMORY_ERROR, /*!< An error occurred during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ DRIVER_ERROR, /*!< A system driver error occurred. */ SYSTEM_ERROR, /*!< A system error occurred. */ THREAD_ERROR /*!< A thread error occurred. */ }; //! The constructor. RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) : std::runtime_error(message), type_(type) {} //! Prints thrown error message to stderr. virtual void printMessage( void ) const { std::cerr << '\n' << what() << "\n\n"; } //! Returns the thrown error message type. virtual const Type& getType(void) const { return type_; } //! Returns the thrown error message string. virtual const std::string getMessage(void) const { return std::string(what()); } protected: Type type_; }; //! RtAudio error callback function prototype. /*! \param type Type of error. \param errorText Error description. */ typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); // **************************************************************** // // // RtAudio class declaration. // // RtAudio is a "controller" used to select an available audio i/o // interface. It presents a common API for the user to call but all // functionality is implemented by the class RtApi and its // subclasses. RtAudio creates an instance of an RtApi subclass // based on the user's API choice. If no choice is made, RtAudio // attempts to make a "logical" API selection. // // **************************************************************** // class RtApi; class RTAUDIO_DLL_PUBLIC RtAudio { public: //! Audio API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ LINUX_PULSE, /*!< The Linux PulseAudio API. */ LINUX_OSS, /*!< The Linux Open Sound System API. */ UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_DS, /*!< The Microsoft DirectSound API. */ RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ NUM_APIS /*!< Number of values in this enum. */ }; //! The public device information structure for returning queried values. struct DeviceInfo { bool probed; /*!< true if the device capabilities were successfully probed. */ std::string name; /*!< Character string device identifier. */ unsigned int outputChannels{}; /*!< Maximum output channels supported by device. */ unsigned int inputChannels{}; /*!< Maximum input channels supported by device. */ unsigned int duplexChannels{}; /*!< Maximum simultaneous input/output channels supported by device. */ bool isDefaultOutput{false}; /*!< true if this is the default output device. */ bool isDefaultInput{false}; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ unsigned int preferredSampleRate{}; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ RtAudioFormat nativeFormats{}; /*!< Bit mask of supported data formats. */ }; //! The structure for specifying input or output stream parameters. struct StreamParameters { unsigned int deviceId{}; /*!< Device index (0 to getDeviceCount() - 1). */ unsigned int nChannels{}; /*!< Number of channels. */ unsigned int firstChannel{}; /*!< First channel index on device (default = 0). */ }; //! The structure for specifying stream options. /*! The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows DirectSound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME flag is set. It defines the thread's realtime priority. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. The \c numberOfBuffers parameter can be used to control stream latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs only. A value of two is usually the smallest allowed. Larger numbers can potentially result in more robust stream performance, though likely at the cost of stream latency. The value set by the user is replaced during execution of the RtAudio::openStream() function by the value actually used by the system. The \c streamName parameter can be used to set the client name when using the Jack API. By default, the client name is set to RtApiJack. However, if you wish to create multiple instances of RtAudio with Jack, each instance must have a unique client name. */ struct StreamOptions { RtAudioStreamFlags flags{}; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ unsigned int numberOfBuffers{}; /*!< Number of stream buffers. */ std::string streamName; /*!< A stream name (currently used only in Jack). */ int priority{}; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ }; //! A static function to determine the current RtAudio version. static std::string getVersion( void ); //! A static function to determine the available compiled audio APIs. /*! The values returned in the std::vector can be compared against the enumerated list values. Note that there can be more than one API compiled for certain operating systems. */ static void getCompiledApi( std::vector &apis ); //! Return the name of a specified compiled audio API. /*! This obtains a short lower-case name used for identification purposes. This value is guaranteed to remain identical across library versions. If the API is unknown, this function will return the empty string. */ static std::string getApiName( RtAudio::Api api ); //! Return the display name of a specified compiled audio API. /*! This obtains a long name used for display purposes. If the API is unknown, this function will return the empty string. */ static std::string getApiDisplayName( RtAudio::Api api ); //! Return the compiled audio API having the given name. /*! A case insensitive comparison will check the specified name against the list of compiled APIs, and return the one which matches. On failure, the function returns UNSPECIFIED. */ static RtAudio::Api getCompiledApiByName( const std::string &name ); //! The class constructor. /*! The constructor performs minor initialization tasks. An exception can be thrown if no API support is compiled. If no API argument is specified and multiple API support has been compiled, the default order of use is JACK, ALSA, OSS (Linux systems) and ASIO, DS (Windows systems). */ RtAudio( RtAudio::Api api=UNSPECIFIED ); //! The destructor. /*! If a stream is running or open, it will be stopped and closed automatically. */ ~RtAudio(); //! Returns the audio API specifier for the current instance of RtAudio. RtAudio::Api getCurrentApi( void ); //! A public function that queries for the number of audio devices available. /*! This function performs a system query of available devices each time it is called, thus supporting devices connected \e after instantiation. If a system error occurs during processing, a warning will be issued. */ unsigned int getDeviceCount( void ); //! Return an RtAudio::DeviceInfo structure for a specified device number. /*! Any device integer between 0 and getDeviceCount() - 1 is valid. If an invalid argument is provided, an RtAudioError (type = INVALID_USE) will be thrown. If a device is busy or otherwise unavailable, the structure member "probed" will have a value of "false" and all other members are undefined. If the specified device is the current default input or output device, the corresponding "isDefault" member will have a value of "true". */ RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); //! A function that returns the index of the default output device. /*! If the underlying audio API does not provide a "default device", or if no devices are available, the return value will be 0. Note that this is a valid device identifier and it is the client's responsibility to verify that a device is available before attempting to open a stream. */ unsigned int getDefaultOutputDevice( void ); //! A function that returns the index of the default input device. /*! If the underlying audio API does not provide a "default device", or if no devices are available, the return value will be 0. Note that this is a valid device identifier and it is the client's responsibility to verify that a device is available before attempting to open a stream. */ unsigned int getDefaultInputDevice( void ); //! A public function for opening a stream with the specified parameters. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be opened with the specified parameters or an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if any invalid device ID or channel number parameters are specified. \param outputParameters Specifies output stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For input-only streams, this argument should be NULL. The device ID is an index value between 0 and getDeviceCount() - 1. \param inputParameters Specifies input stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For output-only streams, this argument should be NULL. The device ID is an index value between 0 and getDeviceCount() - 1. \param format An RtAudioFormat specifying the desired sample data format. \param sampleRate The desired sample rate (sample frames per second). \param *bufferFrames A pointer to a value indicating the desired internal buffer size in sample frames. The actual value used by the device is returned via the same pointer. A value of zero can be specified, in which case the lowest allowable value is determined. \param callback A client-defined function that will be invoked when input data is available and/or output data is needed. \param userData An optional pointer to data that can be accessed from within the callback function. \param options An optional pointer to a structure containing various global stream options, including a list of OR'ed RtAudioStreamFlags and a suggested number of stream buffers that can be used to control stream latency. More buffers typically result in more robust performance, though at a cost of greater latency. If a value of zero is specified, a system-specific median value is chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the lowest allowable value is used. The actual value used is returned via the structure argument. The parameter is API dependent. \param errorCallback A client-defined function that will be invoked when an error has occurred. */ void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); //! A function that closes a stream and frees any associated stream memory. /*! If a stream is not open, this function issues a warning and returns (no exception is thrown). */ void closeStream( void ); //! A function that starts a stream. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already running. */ void startStream( void ); //! Stop a stream, allowing any samples remaining in the output queue to be played. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ void stopStream( void ); //! Stop a stream, discarding any samples remaining in the input/output queue. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ void abortStream( void ); //! Returns true if a stream is open and false if not. bool isStreamOpen( void ) const; //! Returns true if the stream is running and false if it is stopped or not open. bool isStreamRunning( void ) const; //! Returns the number of elapsed seconds since the stream was started. /*! If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ double getStreamTime( void ); //! Set the stream time to a time in seconds greater than or equal to 0.0. /*! If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ void setStreamTime( double time ); //! Returns the internal stream latency in sample frames. /*! The stream latency refers to delay in audio input and/or output caused by internal buffering by the audio system and/or hardware. For duplex streams, the returned value will represent the sum of the input and output latencies. If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. If the API does not report latency, the return value will be zero. */ long getStreamLatency( void ); //! Returns actual sample rate in use by the stream. /*! On some systems, the sample rate used may be slightly different than that specified in the stream parameters. If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ unsigned int getStreamSampleRate( void ); //! Specify whether warning messages should be printed to stderr. void showWarnings( bool value = true ); protected: void openRtApi( RtAudio::Api api ); RtApi *rtapi_; }; // Operating system dependent thread functionality. #if defined(_WIN32) || defined(__CYGWIN__) #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; #else // Using pthread library for various flavors of unix. #include typedef pthread_t ThreadHandle; typedef pthread_mutex_t StreamMutex; #endif // Setup for "dummy" behavior if no apis specified. #if !(defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) \ || defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) \ || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__)) #define __RTAUDIO_DUMMY__ #endif // This global structure type is used to pass callback information // between the private RtAudio stream structure and global callback // handling functions. struct CallbackInfo { void *object{}; // Used as a "this" pointer. ThreadHandle thread{}; void *callback{}; void *userData{}; void *errorCallback{}; void *apiInfo{}; // void pointer for API specific callback information bool isRunning{false}; bool doRealtime{false}; int priority{}; }; // **************************************************************** // // // RtApi class declaration. // // Subclasses of RtApi contain all API- and OS-specific code necessary // to fully implement the RtAudio API. // // Note that RtApi is an abstract base class and cannot be // explicitly instantiated. The class RtAudio will create an // instance of an RtApi subclass (RtApiOss, RtApiAlsa, // RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). // // **************************************************************** // #pragma pack(push, 1) class S24 { protected: unsigned char c3[3]; public: S24() {} S24& operator = ( const int& i ) { c3[0] = (unsigned char)(i & 0x000000ff); c3[1] = (unsigned char)((i & 0x0000ff00) >> 8); c3[2] = (unsigned char)((i & 0x00ff0000) >> 16); return *this; } S24( const double& d ) { *this = (int) d; } S24( const float& f ) { *this = (int) f; } S24( const signed short& s ) { *this = (int) s; } S24( const char& c ) { *this = (int) c; } int asInt() { int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); if (i & 0x800000) i |= ~0xffffff; return i; } }; #pragma pack(pop) #if defined( HAVE_GETTIMEOFDAY ) #include #endif #include class RTAUDIO_DLL_PUBLIC RtApi { public: RtApi(); virtual ~RtApi(); virtual RtAudio::Api getCurrentApi( void ) = 0; virtual unsigned int getDeviceCount( void ) = 0; virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; virtual unsigned int getDefaultInputDevice( void ); virtual unsigned int getDefaultOutputDevice( void ); void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ); virtual void closeStream( void ); virtual void startStream( void ) = 0; virtual void stopStream( void ) = 0; virtual void abortStream( void ) = 0; long getStreamLatency( void ); unsigned int getStreamSampleRate( void ); virtual double getStreamTime( void ); virtual void setStreamTime( double time ); bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } void showWarnings( bool value ) { showWarnings_ = value; } protected: static const unsigned int MAX_SAMPLE_RATES; static const unsigned int SAMPLE_RATES[]; enum { FAILURE, SUCCESS }; enum StreamState { STREAM_STOPPED, STREAM_STOPPING, STREAM_RUNNING, STREAM_CLOSED = -50 }; enum StreamMode { OUTPUT, INPUT, DUPLEX, UNINITIALIZED = -75 }; // A protected structure used for buffer conversion. struct ConvertInfo { int channels; int inJump, outJump; RtAudioFormat inFormat, outFormat; std::vector inOffset; std::vector outOffset; }; // A protected structure for audio streams. struct RtApiStream { unsigned int device[2]; // Playback and record, respectively. void *apiHandle; // void pointer for API specific stream handle information StreamMode mode; // OUTPUT, INPUT, or DUPLEX. StreamState state; // STOPPED, RUNNING, or CLOSED char *userBuffer[2]; // Playback and record, respectively. char *deviceBuffer; bool doConvertBuffer[2]; // Playback and record, respectively. bool userInterleaved; bool deviceInterleaved[2]; // Playback and record, respectively. bool doByteSwap[2]; // Playback and record, respectively. unsigned int sampleRate; unsigned int bufferSize; unsigned int nBuffers; unsigned int nUserChannels[2]; // Playback and record, respectively. unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. unsigned int channelOffset[2]; // Playback and record, respectively. unsigned long latency[2]; // Playback and record, respectively. RtAudioFormat userFormat; RtAudioFormat deviceFormat[2]; // Playback and record, respectively. StreamMutex mutex; CallbackInfo callbackInfo; ConvertInfo convertInfo[2]; double streamTime; // Number of elapsed seconds since the stream started. #if defined(HAVE_GETTIMEOFDAY) struct timeval lastTickTimestamp; #endif RtApiStream() :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } }; typedef S24 Int24; typedef signed short Int16; typedef signed int Int32; typedef float Float32; typedef double Float64; std::ostringstream errorStream_; std::string errorText_; bool showWarnings_; RtApiStream stream_; bool firstErrorOccurred_; /*! Protected, api-specific method that attempts to open a device with the given parameters. This function MUST be implemented by all subclasses. If an error is encountered during the probe, a "warning" message is reported and FAILURE is returned. A successful probe is indicated by a return value of SUCCESS. */ virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); //! A protected function used to increment the stream time. void tickStreamTime( void ); //! Protected common method to clear an RtApiStream structure. void clearStreamInfo(); /*! Protected common method that throws an RtAudioError (type = INVALID_USE) if a stream is not open. */ void verifyStream( void ); //! Protected common error method to allow global control over error handling. void error( RtAudioError::Type type ); /*! Protected method used to perform format, channel number, and/or interleaving conversions between the user and device buffers. */ void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); //! Protected common method used to perform byte-swapping on buffers. void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); //! Protected common method that returns the number of bytes for a given format. unsigned int formatBytes( RtAudioFormat format ); //! Protected common method that sets up the parameters for buffer conversion. void setConvertInfo( StreamMode mode, unsigned int firstChannel ); }; // **************************************************************** // // // Inline RtAudio definitions. // // **************************************************************** // inline RtAudio::Api RtAudio :: getCurrentApi( void ) { return rtapi_->getCurrentApi(); } inline unsigned int RtAudio :: getDeviceCount( void ) { return rtapi_->getDeviceCount(); } inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } inline unsigned int RtAudio :: getDefaultInputDevice( void ) { return rtapi_->getDefaultInputDevice(); } inline unsigned int RtAudio :: getDefaultOutputDevice( void ) { return rtapi_->getDefaultOutputDevice(); } inline void RtAudio :: closeStream( void ) { return rtapi_->closeStream(); } inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } inline bool RtAudio :: isStreamOpen( void ) const { return rtapi_->isStreamOpen(); } inline bool RtAudio :: isStreamRunning( void ) const { return rtapi_->isStreamRunning(); } inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } // RtApi Subclass prototypes. #if defined(__MACOSX_CORE__) #include class RtApiCore: public RtApi { public: RtApiCore(); ~RtApiCore(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::MACOSX_CORE; } unsigned int getDeviceCount( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; unsigned int getDefaultOutputDevice( void ) override; unsigned int getDefaultInputDevice( void ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; static const char* getErrorCode( OSStatus code ); }; #endif #if defined(__UNIX_JACK__) class RtApiJack: public RtApi { public: RtApiJack(); ~RtApiJack(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::UNIX_JACK; } unsigned int getDeviceCount( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( unsigned long nframes ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; bool shouldAutoconnect_; }; #endif #if defined(__WINDOWS_ASIO__) class RtApiAsio: public RtApi { public: RtApiAsio(); ~RtApiAsio(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_ASIO; } unsigned int getDeviceCount( void ) override; unsigned int getDefaultOutputDevice( void ) override; unsigned int getDefaultInputDevice( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( long bufferIndex ); private: std::vector devices_; void saveDeviceInfo( void ); bool coInitialized_; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__WINDOWS_DS__) class RtApiDs: public RtApi { public: RtApiDs(); ~RtApiDs(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_DS; } unsigned int getDeviceCount( void ) override; unsigned int getDefaultOutputDevice( void ) override; unsigned int getDefaultInputDevice( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: bool coInitialized_; bool buffersRolling; long duplexPrerollBytes; std::vector dsDevices; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__WINDOWS_WASAPI__) struct IMMDeviceEnumerator; class RtApiWasapi : public RtApi { public: RtApiWasapi(); virtual ~RtApiWasapi(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_WASAPI; } unsigned int getDeviceCount( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; private: bool coInitialized_; IMMDeviceEnumerator* deviceEnumerator_; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) override; static DWORD WINAPI runWasapiThread( void* wasapiPtr ); static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); void wasapiThread(); }; #endif #if defined(__LINUX_ALSA__) class RtApiAlsa: public RtApi { public: RtApiAlsa(); ~RtApiAlsa(); RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_ALSA; } unsigned int getDeviceCount( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: std::vector devices_; void saveDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__LINUX_PULSE__) class RtApiPulse: public RtApi { public: ~RtApiPulse(); RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_PULSE; } unsigned int getDeviceCount( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: void collectDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__LINUX_OSS__) class RtApiOss: public RtApi { public: RtApiOss(); ~RtApiOss(); RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_OSS; } unsigned int getDeviceCount( void ) override; RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) override; void closeStream( void ) override; void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__RTAUDIO_DUMMY__) class RtApiDummy: public RtApi { public: RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } RtAudio::Api getCurrentApi( void ) override { return RtAudio::RTAUDIO_DUMMY; } unsigned int getDeviceCount( void ) override { return 0; } RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) override { RtAudio::DeviceInfo info; return info; } void closeStream( void ) override {} void startStream( void ) override {} void stopStream( void ) override {} void abortStream( void ) override {} private: bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) override { return false; } }; #endif #endif // Indentation settings for Vim and Emacs // // Local Variables: // c-basic-offset: 2 // indent-tabs-mode: nil // End: // // vim: et sts=2 sw=2 audmes-2025.04.05/sma_2d.cpp000066400000000000000000000063441477556201600152640ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // Name: sma-2d // Purpose: Simple Moving average over N values for R rows ////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2023 Johannes Linkels * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "sma_2d.h" #include SMA_2D::SMA_2D(int numRecords, int numAverage) { Init(numRecords, numAverage); } SMA_2D::~SMA_2D() { index.clear(); sum.clear(); numSummed.clear(); prevValues.clear(); } int SMA_2D::AddVal(int recordNum, double val) { if (!(recordNum < m_NumRecords)) { return 1; } // prevValues is a ring buffer, holding the last N values, // with N the length of the average numAverage. // A new value is added to the sum, the oldest value // is substracted. The new value is stored in prevValues // and remains there for N itereations. int curIndex = index[recordNum]; sum[recordNum] -= prevValues[recordNum][curIndex]; sum[recordNum] += val; prevValues[recordNum][curIndex] = val; if (++curIndex == m_NumAverage) { curIndex = 0; } if (++numSummed[recordNum] > m_NumAverage) { numSummed[recordNum] = m_NumAverage; } index[recordNum] = curIndex; return 0; } double SMA_2D::GetSMA(int recordNum) { // Calculate the average only over the values which are // added. If not, it would take until all values are added // until the average shows the final value. // If no samples have been added yet, prevent division by zero. #ifdef _DEBUG if (recordNum >= m_NumRecords) abort(); #endif if (numSummed[recordNum] == 0) { return 0.0; } double sma = sum[recordNum] / numSummed[recordNum]; return sma; } int SMA_2D::GetNumAverage() { return m_NumAverage; } int SMA_2D::GetNumSummed(int recordNum) { return numSummed[recordNum]; } void SMA_2D::SetNumAverage(int numAverage) { // Alters the length of the averaging window // without changing the number of records // This re-initializes the vectors Init(m_NumRecords, numAverage); } void SMA_2D::SetNumRecords(int numRecords) { // Alters the number of records without changing // the length of the averaging window // This re-initializes the vectors Init(numRecords, m_NumAverage); } void SMA_2D::Init(int numRecords, int numAverage) { m_NumRecords = numRecords; m_NumAverage = numAverage; index.clear(); sum.clear(); numSummed.clear(); prevValues.clear(); index.resize(numRecords, 0); sum.resize(numRecords, 0); numSummed.resize(numRecords, 0); prevValues.resize(numRecords, std::vector(numAverage)); } audmes-2025.04.05/sma_2d.h000066400000000000000000000044461477556201600147320ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////////////// // Name: sma-2d // Purpose: Simple Moving average over N values for R rows ///////////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2023 Johannes Linkels * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef _SMA_2D_ #define _SMA_2D_ #include #include class SMA_2D { public: /// Constructors SMA_2D(int numRecords, int numAverage); ~SMA_2D(); /// (Re)Initializes the data values void Init(int numRecords, int numAverage); /// Adds a new value to the average at record recordNum int AddVal(int recordNum, double val); /// Gets the current moving average value at record recordNum double GetSMA(int recordNum); /// Get the number of values in the moving average int GetNumAverage(); /// Get the number of values summed; int GetNumSummed(int recordNum); /// Set the length of the moving average void SetNumAverage(int numAverage); /// Set the number of values summed; void SetNumRecords(int numRecords); private: int m_NumRecords; // number of records (samples) int m_NumAverage; // number of values used for the average std::vector index; // index points to latest value in each record std::vector numSummed; // number of values summed, avg = sum/numSummed std::vector sum; // current sum of values for each record std::vector> prevValues; // the actual 2D store }; #endif // _SMA_2D audmes-2025.04.05/tests/000077500000000000000000000000001477556201600145465ustar00rootroot00000000000000audmes-2025.04.05/tests/CMakeLists.txt000066400000000000000000000001141477556201600173020ustar00rootroot00000000000000add_executable (sma_2d_test EXCLUDE_FROM_ALL sma_2d_test.cpp ../sma_2d.cpp) audmes-2025.04.05/tests/sma_2d_test.cpp000066400000000000000000000076721477556201600174720ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // RWAudio_IO.cpp: impementation of audio interface // ////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2008 Vaclav Peroutka * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../sma_2d.h" //#include #include #include #define EPSILON 1e-6 int main() { SMA_2D realAvg(3, 5); std::cout << "Hello World\n"; // Check numRecords out of bounds if (!realAvg.AddVal(3, 14.3)) { std::cout << "Out of bounds check recordNum failed\n"; } // Check inbounds numRecords passes if (realAvg.AddVal(2, 14)) { std::cout << "In bounds check recordNum failed\n"; } // Re-init the number of records, and check the number of records realAvg.SetNumRecords(4); if (!realAvg.AddVal(4, 14.3)) { std::cout << "Out of bounds check recordNum failed\n"; } if (realAvg.AddVal(3, 14)) { std::cout << "In bounds check recordNum failed\n"; } // Check average when less than all samples are added realAvg.AddVal(1, 14.2); realAvg.AddVal(1, 14.4); if (fabs(realAvg.GetSMA(1) - 14.3) > EPSILON) { std::cout << "Average with 2 samples failed\n"; } // Check re-initialization realAvg.Init(3, 5); if (fabs(realAvg.GetSMA(1)) > EPSILON) { std::cout << "Re-initialization failed, value: " << std::to_string(realAvg.GetSMA(1)) << "\n"; } // Check average with exactly 5 samples for (int i = 0; i < 5; i++) { realAvg.AddVal(1, (double)i); } if (fabs(realAvg.GetSMA(1) - 2.0) > EPSILON) { std::cout << "Average with full average buffer failed, value: " << std::to_string(realAvg.GetSMA(1)) << "\n"; } // Check continuation of average after 5 samples realAvg.AddVal(1, 5); if (fabs(realAvg.GetSMA(1) - 3.0) > EPSILON) { std::cout << "Average with continue average buffer failed, value: " << std::to_string(realAvg.GetSMA(1)) << "\n"; } // Check other record for zero and division by zero error if (fabs(realAvg.GetSMA(2) > EPSILON)) { std::cout << "Record with no entries remain zero failed, value: " << std::to_string(realAvg.GetSMA(2)) << "\n"; } // Re-init with a different average window lenght realAvg.SetNumAverage(6); for (int i = 0; i < 6; i++) { realAvg.AddVal(1, (double)i); } if (fabs(realAvg.GetSMA(1) - 2.5) > EPSILON) { std::cout << "Re-init with different averagen length failed, value: " << std::to_string(realAvg.GetSMA(1)) << "\n"; } // Let's test with some real values now realAvg.Init(3, 5); std::vector outputs{10.0, 10.5, 10.0, 11.0, 10.6, 11.0, 11.4, 11.2, 11.8, 12.0, 9.6, 7.0, 5.4, 2.0, 0.0}; std::vector inputs{10.0, 11.0, 9.0, 14.0, 9.0, 12.0, 13.0, 8.0, 17.0, 10.0, 0, 0, 0, 0, 0}; for (int i = 0; i < inputs.size(); i++) { realAvg.AddVal(1, inputs[i]); std::cout << "iterator: " << std::to_string(i) << " SMA: " << std::to_string(realAvg.GetSMA(1)) << " num: " << std::to_string(realAvg.GetNumSummed(1)) << "\n"; if (fabs(realAvg.GetSMA(1) - outputs[i]) > EPSILON) { std::cout << "Calculating average failed in step: " << std::to_string(i) << "\n"; } } return 0; }