/********************************************************************** Audacity: A Digital Audio Editor AudioUnitEffect.cpp Dominic Mazzoni *******************************************************************//** \class AudioUnitEffect \brief An Effect class that handles a wide range of effects. ??Mac only?? *//****************************************************************//** \class AudioUnitGUIControl \brief Used with AudioUnitEffect *//****************************************************************//** \class AudioUnitDialog \brief A Dialog used with AudioUnitEffect *//*******************************************************************/ #include "AudioUnitEffect.h" #include #include #include #include #include #include #include #include #if ((wxMAJOR_VERSION == 2) && (wxMINOR_VERSION < 6)) #error Audio Units support requires wxMac 2.6 #endif #include // // Forward declarations of GUI classes // class AudioUnitGUIControl : public wxControl { public: inline AudioUnitGUIControl(wxWindow *parent, wxWindowID id, wxPoint pos, wxSize size, ControlRef controlRef) { Create(parent, id, pos, size, controlRef); } virtual ~AudioUnitGUIControl(); bool Create(wxWindow *parent, wxWindowID id, wxPoint pos, wxSize size, ControlRef controlRef); void OnMouse(wxMouseEvent &event); private: short GetModifiers(wxMouseEvent &event); DECLARE_EVENT_TABLE() }; class AudioUnitDialog : public wxDialog { public: AudioUnitDialog(wxWindow *parent, wxWindowID id, wxString title, AudioUnit unit, AudioUnitCarbonView carbonView, Effect *effect); virtual ~AudioUnitDialog(); void RemoveHandler(); void OnOK(wxCommandEvent &event); void OnCancel(wxCommandEvent &event); void OnPreview(wxCommandEvent &event); private: AudioUnit mUnit; AudioUnitGUIControl *mGUIControl; wxBoxSizer *mMainSizer; Effect *mEffect; EventHandlerRef mHandlerRef; EventHandlerUPP mHandlerUPP; DECLARE_EVENT_TABLE() }; // // AudioUnitEffect // AudioUnitEffect::AudioUnitEffect(wxString name, Component component): mName(name), mComponent(component) { SetEffectFlags(PLUGIN_EFFECT | PROCESS_EFFECT); mUnit = NULL; OpenAComponent(mComponent, &mUnit); } AudioUnitEffect::~AudioUnitEffect() { if (mUnit) { CloseComponent(mUnit); } } wxString AudioUnitEffect::GetEffectName() { return mName; } std::set AudioUnitEffect::GetEffectCategories() { return std::set(); } wxString AudioUnitEffect::GetEffectIdentifier() { wxStringTokenizer st(mName, wxT(" ")); wxString id; // CamelCase the name while (st.HasMoreTokens()) { wxString tok = st.GetNextToken(); id += tok.Left(1).MakeUpper() + tok.Mid(1); } return id; } wxString AudioUnitEffect::GetEffectAction() { return wxString::Format(_("Performing Effect: %s"), mName.c_str()); } bool AudioUnitEffect::Init() { if (!mUnit) { return false; } mSupportsMono = SetRateAndChannels(mUnit, 1, mProjectRate); mSupportsStereo = SetRateAndChannels(mUnit, 2, mProjectRate); if (!mSupportsMono && !mSupportsStereo) { mSupportsMono = SetRateAndChannels(mUnit, 1, 44100.0); mSupportsStereo = SetRateAndChannels(mUnit, 2, 44100.0); if (!mSupportsMono && !mSupportsStereo) { printf("Audio Unit doesn't support mono or stereo.\n"); return false; } } return true; } bool AudioUnitEffect::PromptUser() { OSErr result; ComponentDescription desc; Component carbonViewComponent = NULL; AudioUnitCarbonView carbonView = NULL; GetComponentInfo(mComponent, &desc, 0, 0, 0); carbonViewComponent = GetCarbonViewComponent(desc.componentSubType); result = OpenAComponent(carbonViewComponent, &carbonView); if (result != 0) { printf("Couldn't open carbon view component\n"); return false; } AudioUnitDialog dlog(mParent, -1, mName, mUnit, carbonView, this); dlog.CentreOnParent(); dlog.ShowModal(); CloseComponent(carbonView); return (dlog.GetReturnCode() == wxID_OK); } bool AudioUnitEffect::Process() { bool bGoodResult = true; CopyInputTracks(); TrackListIterator iter(mOutputTracks); int count = 0; Track *left = iter.First(); Track *right; while(left) { sampleCount lstart, rstart; sampleCount len; GetSamples((WaveTrack *)left, &lstart, &len); right = NULL; if (left->GetLinked() && mSupportsStereo) { right = iter.Next(); GetSamples((WaveTrack *)right, &rstart, &len); } bool success = false; if (!mSupportsStereo && right) { // If the effect is mono, apply to each channel separately success = ProcessStereo(count, (WaveTrack *)left, NULL, lstart, 0, len); if (success) success = ProcessStereo(count, (WaveTrack *)right, NULL, rstart, 0, len); } else success = ProcessStereo(count, (WaveTrack *)left, (WaveTrack *)right, lstart, rstart, len); if (!success) { bGoodResult = false; break; } left = iter.Next(); count++; } ReplaceProcessedTracks(bGoodResult); return bGoodResult; } void AudioUnitEffect::End() { } bool AudioUnitEffect::SetRateAndChannels(AudioUnit unit, int numChannels, Float64 sampleRate) { AudioStreamBasicDescription streamFormat; ComponentResult auResult; auResult = AudioUnitSetProperty(unit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Global, 0, &sampleRate, sizeof(Float64)); if (auResult != 0) { printf("Didn't accept sample rate\n"); return false; } streamFormat.mSampleRate = sampleRate; streamFormat.mFormatID = kAudioFormatLinearPCM; streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; streamFormat.mBitsPerChannel = 32; streamFormat.mChannelsPerFrame = numChannels; streamFormat.mFramesPerPacket = 1; streamFormat.mBytesPerFrame = 4; streamFormat.mBytesPerPacket = 4; auResult = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(AudioStreamBasicDescription)); if (auResult != 0) return false; auResult = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, sizeof(AudioStreamBasicDescription)); if (auResult != 0) return false; auResult = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 0, &streamFormat, sizeof(AudioStreamBasicDescription)); if (auResult != 0) { printf("Didn't accept global stream format\n"); return false; } return true; } bool AudioUnitEffect::CopyParameters(AudioUnit srcUnit, AudioUnit dstUnit) { ComponentResult auResult; int numParameters, i; AudioUnitParameterID *parameters; Float32 parameterValue; UInt32 size; // Get number of parameters by passing NULL in the data field and // getting back the size of the parameter list size = 0; auResult = AudioUnitGetProperty(srcUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, NULL, &size); if (auResult != 0) { printf("Couldn't get number of parameters\n"); return false; } // Now get the list of all parameter IDs numParameters = size / sizeof(AudioUnitParameterID); parameters = new AudioUnitParameterID[numParameters]; auResult = AudioUnitGetProperty(srcUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, parameters, &size); if (auResult != 0) { printf("Couldn't get parameter list\n"); delete[] parameters; return false; } // Copy the parameters from the main unit to the unit specific to // this track for(i=0; iGetRate(); AudioUnit trackUnit = mUnit; AURenderCallbackStruct callbackStruct; AudioTimeStamp timeStamp; AudioBufferList *bufferList = NULL; ComponentResult auResult; int waveTrackBlockSize; UInt32 size; UInt32 unitBlockSize; float *leftBuffer; float *rightBuffer; bool success = true; if (!SetRateAndChannels(trackUnit, numChannels, sampleRate)) { printf("Unable to setup audio unit for channels=%d rate=%.1f\n", numChannels, sampleRate); return false; } unitBlockSize = 0; size = sizeof(UInt32); auResult = AudioUnitGetProperty(trackUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &unitBlockSize, &size); if (unitBlockSize == 0 || auResult != 0) { printf("Warning: didn't get audio unit's MaximumFramesPerSlice\n"); printf("Trying to set MaximumFramesPerSlice to 512\n"); unitBlockSize = 512; auResult = AudioUnitSetProperty(trackUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &unitBlockSize, sizeof(UInt32)); if (auResult != 0) printf("Unable to set MaximumFramesPerSlice, rendering may fail...\n"); } auResult = AudioUnitInitialize(trackUnit); if (auResult != 0) { printf("Couldn't initialize audio unit\n"); return false; } auResult = AudioUnitReset(trackUnit, kAudioUnitScope_Global, 0); if (auResult != 0) { printf("Reset failed.\n"); AudioUnitUninitialize(trackUnit); return false; } callbackStruct.inputProc = SimpleAudioRenderCallback; callbackStruct.inputProcRefCon = this; auResult = AudioUnitSetProperty(trackUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackStruct, sizeof(AURenderCallbackStruct)); if (auResult != 0) { printf("Setting input render callback failed.\n"); AudioUnitUninitialize(trackUnit); return false; } memset(&timeStamp, 0, sizeof(AudioTimeStamp)); timeStamp.mSampleTime = 0; // This is a double-precision number that should // accumulate the number of frames processed so far timeStamp.mFlags = kAudioTimeStampSampleTimeValid; waveTrackBlockSize = left->GetMaxBlockSize() * 2; leftBuffer = NULL; rightBuffer = NULL; mLeftBufferForCallback = NULL; mRightBufferForCallback = NULL; if (left) { leftBuffer = new float[waveTrackBlockSize]; } if (right) { rightBuffer = new float[waveTrackBlockSize]; } bufferList = (AudioBufferList *)malloc(sizeof(UInt32) + numChannels * sizeof(AudioBuffer)); bufferList->mNumberBuffers = numChannels; sampleCount originalLen = len; sampleCount ls = lstart; sampleCount rs = rstart; while (len) { int block = waveTrackBlockSize; if (block > len) { block = len; } if (left) { left->Get((samplePtr)leftBuffer, floatSample, ls, block); } if (right) { right->Get((samplePtr)rightBuffer, floatSample, rs, block); } success = DoRender(trackUnit, numChannels, leftBuffer, rightBuffer, block, unitBlockSize, &timeStamp, bufferList); if (!success) { break; } if (left) { left->Set((samplePtr)leftBuffer, floatSample, ls, block); } if (right) { right->Set((samplePtr)rightBuffer, floatSample, rs, block); } len -= block; ls += block; rs += block; if (left && right) { if (TrackGroupProgress(count, (ls-lstart)/(double)originalLen)) { success = false; break; } } else { if (TrackProgress(count, (ls-lstart)/(double)originalLen)) { success = false; break; } } } if (bufferList) { free(bufferList); } if (leftBuffer) { delete[] leftBuffer; } if (rightBuffer) { delete[] rightBuffer; } AudioUnitUninitialize(trackUnit); return success; } bool AudioUnitEffect::DoRender(AudioUnit unit, int numChannels, float *leftBuffer, float *rightBuffer, int len, int unitBlockSize, AudioTimeStamp *timeStamp, AudioBufferList *bufferList) { AudioUnitRenderActionFlags flags; ComponentResult auResult; int block; for (int i = 0; i < len; i += block) { block = unitBlockSize; if (i + block > len) { block = len - i; } for (int j = 0; jmBuffers[j].mNumberChannels = 1; bufferList->mBuffers[j].mData = NULL; bufferList->mBuffers[j].mDataByteSize = sizeof(float) * block; } if (leftBuffer) { mLeftBufferForCallback = &leftBuffer[i]; bufferList->mBuffers[0].mData = mLeftBufferForCallback; } if (rightBuffer) { mRightBufferForCallback = &rightBuffer[i]; bufferList->mBuffers[1].mData = mRightBufferForCallback; } flags = 0; auResult = AudioUnitRender(unit, &flags, timeStamp, 0, block, bufferList); if (auResult != 0) { printf("Render failed: %d %4.4s\n", (int)auResult, (char *)&auResult); return false; } timeStamp->mSampleTime += block; } return true; } // static OSStatus AudioUnitEffect::SimpleAudioRenderCallback (void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { AudioUnitEffect *This = (AudioUnitEffect *)inRefCon; if (This->mLeftBufferForCallback) { ioData->mBuffers[0].mData = This->mLeftBufferForCallback; } if (This->mRightBufferForCallback) { ioData->mBuffers[1].mData = This->mRightBufferForCallback; } return 0; } Component AudioUnitEffect::GetCarbonViewComponent(OSType subtype) { ComponentDescription desc; Component component; desc.componentType = kAudioUnitCarbonViewComponentType; // 'auvw' desc.componentSubType = subtype; desc.componentManufacturer = 0; desc.componentFlags = 0; desc.componentFlagsMask = 0; // First see if we can find a carbon view designed specifically for this // plug-in: component = FindNextComponent(NULL, &desc); if (component) return component; // If not, grab the generic carbon view, which will create a GUI for // any Audio Unit. desc.componentSubType = kAUCarbonViewSubType_Generic; component = FindNextComponent(NULL, &desc); return component; } // // AudioUnitGUIControl methods // BEGIN_EVENT_TABLE(AudioUnitGUIControl, wxControl) EVT_MOUSE_EVENTS(AudioUnitGUIControl::OnMouse) END_EVENT_TABLE() AudioUnitGUIControl::~AudioUnitGUIControl() { // Don't want to dispose of it... m_peer = NULL; } bool AudioUnitGUIControl::Create(wxWindow *parent, wxWindowID id, wxPoint pos, wxSize size, ControlRef controlRef) { m_macIsUserPane = FALSE ; if ( !wxControl::Create(parent, id, pos, size, 0, wxDefaultValidator, wxString(wxT("AudioUnitControl"))) ) return false; m_peer = new wxMacControl(this, controlRef); Rect outBounds; GetControlBounds(controlRef, &outBounds); pos.x = outBounds.left; pos.y = outBounds.top; size.x = outBounds.right - outBounds.left; size.y = outBounds.bottom - outBounds.top; MacPostControlCreate(pos, size); return true; } short AudioUnitGUIControl::GetModifiers(wxMouseEvent &event) { short modifiers = 0; if ( !event.m_leftDown && !event.m_rightDown ) modifiers |= btnState ; if ( event.m_shiftDown ) modifiers |= shiftKey ; if ( event.m_controlDown ) modifiers |= controlKey ; if ( event.m_altDown ) modifiers |= optionKey ; if ( event.m_metaDown ) modifiers |= cmdKey ; return modifiers; } void AudioUnitGUIControl::OnMouse(wxMouseEvent &event) { int x = event.m_x ; int y = event.m_y ; MacClientToRootWindow( &x , &y ) ; Point localwhere ; ControlHandle control; localwhere.h = x ; localwhere.v = y ; short modifiers = GetModifiers(event); if (event.GetEventType() == wxEVT_LEFT_DOWN || event.GetEventType() == wxEVT_LEFT_DCLICK ) { #if ((wxMAJOR_VERSION == 2) && (wxMINOR_VERSION <= 4)) WindowRef rootWindow = (WindowRef)GetParent()->MacGetRootWindow(); #else WindowRef rootWindow = (WindowRef)GetParent()->MacGetTopLevelWindowRef(); #endif ControlPartCode code; code = FindControl(localwhere, rootWindow, &control); if (code) { Rect outBounds; GetControlBounds((ControlRef)control, &outBounds); code = ::HandleControlClick(control, localwhere, modifiers, (ControlActionUPP)-1) ; } } } // // AudioUnitDialog methods // // Event handler to capture the window close event static const EventTypeSpec eventList[] = { {kEventClassWindow, kEventWindowClose}, }; static pascal OSStatus EventHandler(EventHandlerCallRef handler, EventRef event, void *data) { OSStatus result = eventNotHandledErr; AudioUnitDialog *dlg = (AudioUnitDialog *)data; if (GetEventClass(event) == kEventClassWindow && GetEventKind(event) == kEventWindowClose) { dlg->RemoveHandler(); dlg->Close(); result = noErr; } return result; } void EventListener(void *inUserData, AudioUnitCarbonView inView, const AudioUnitParameter *inParameter, AudioUnitCarbonViewEventID inEvent, const void *inEventParam) { // We're not actually using this yet... } enum { PreviewID = 1 }; BEGIN_EVENT_TABLE(AudioUnitDialog, wxDialog) EVT_BUTTON(wxID_OK, AudioUnitDialog::OnOK) EVT_BUTTON(wxID_CANCEL, AudioUnitDialog::OnCancel) EVT_BUTTON(PreviewID, AudioUnitDialog::OnPreview) END_EVENT_TABLE() AudioUnitDialog::AudioUnitDialog(wxWindow *parent, wxWindowID id, wxString title, AudioUnit unit, AudioUnitCarbonView carbonView, Effect *effect): mUnit(unit), mEffect(effect), mHandlerUPP(NULL), mHandlerRef(NULL) { long style = wxDEFAULT_DIALOG_STYLE; #if ((wxMAJOR_VERSION == 2) && (wxMINOR_VERSION <= 4)) wxDialog::Create(parent, id, title, wxDefaultPosition, wxSize(500, 400), style, title); #else // wxMac 2.5 version, all of this is just to attempt to turn off // compositing... // From wxDialog::Create SetExtraStyle(GetExtraStyle() | wxTOPLEVEL_EX_DIALOG); style |= wxTAB_TRAVERSAL; // From wxTopLevelWindow::Create wxTopLevelWindow::Init(); m_windowStyle = style; // We added this line because many plug-ins are not happy with // compositing...Requires a patch to wx such that the line // "attr |= kWindowCompositingAttribute" only happens // if m_macUsesCompositing is true... // // LL: Unfortunately, wxWidgets 2.7+ doesn't have a direct // way to disable compositing, so bypass it until a way // is found. #if !wxCHECK_VERSION(2, 7, 0) m_macUsesCompositing = false; #endif // Rest of wxTopLevelWindow::Create SetName(title); m_windowId = id == -1 ? NewControlId() : id; MacCreateRealWindow(title, wxDefaultPosition, wxSize(500, 400), MacRemoveBordersFromStyle(style) , title) ; SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); wxTopLevelWindows.Append(this); if ( parent ) parent->AddChild(this); #endif // // On to our own code // WindowRef windowRef = (WindowRef)MacGetWindowRef(); ComponentResult auResult; ControlRef rootControl = NULL; GetRootControl(windowRef, &rootControl); int width = 500; int height = 400; Float32Point location = {0, 0}; Float32Point size = {width, height}; ControlRef audioUnitControl = NULL; auResult = AudioUnitCarbonViewCreate(carbonView, unit, windowRef, rootControl, &location, &size, &audioUnitControl); AudioUnitCarbonViewSetEventListener(carbonView, EventListener, this); mMainSizer = new wxBoxSizer(wxVERTICAL); if (auResult == 0) { mGUIControl = new AudioUnitGUIControl(this, -1, wxPoint(0, 0), wxSize(width, height), audioUnitControl); // Eventually, to handle resizing controls, call: // AudioUnitCarbonViewSetEventListener with the event: // kEventControlBoundsChanged mMainSizer->Add(mGUIControl, 1, wxEXPAND); mGUIControl->SetFocus(); } else mGUIControl = NULL; wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL); wxButton *preview = new wxButton(this, PreviewID, _("Pre&view")); hSizer->Add(preview, 0, wxALL, 10); hSizer->Add(10, 10); wxButton *ok = new wxButton(this, wxID_OK, _("OK")); ok->SetDefault(); ok->SetFocus(); hSizer->Add(ok, 0, wxALL, 10); wxButton *cancel = new wxButton(this, wxID_CANCEL, _("&Cancel")); hSizer->Add(cancel, 0, wxALL, 10); mMainSizer->Add(hSizer, 0, wxALIGN_CENTER); this->SetAutoLayout(true); this->SetSizer(mMainSizer); mMainSizer->Fit(this); mMainSizer->SetSizeHints(this); // Some VST effects do not work unless the default handler is removed since // it captures many of the events that the plugins need. But, it must be // done last since proper window sizing will not occur otherwise. ::RemoveEventHandler((EventHandlerRef)MacGetEventHandler()); // Install a bare minimum handler so we can capture the window close event. If // it's not captured, we will crash at Audacity termination since the window // is still on the wxWidgets toplevel window lists, but it's already gone. mHandlerUPP = NewEventHandlerUPP(EventHandler); InstallWindowEventHandler(windowRef, mHandlerUPP, GetEventTypeCount(eventList), eventList, this, &mHandlerRef); } AudioUnitDialog::~AudioUnitDialog() { RemoveHandler(); } void AudioUnitDialog::RemoveHandler() { if (mHandlerRef) { ::RemoveEventHandler(mHandlerRef); mHandlerRef = NULL; MacInstallTopLevelWindowEventHandler(); } if (mHandlerUPP) { DisposeEventHandlerUPP(mHandlerUPP); mHandlerUPP = NULL; } } void AudioUnitDialog::OnOK(wxCommandEvent &event) { EndModal(wxID_OK); } void AudioUnitDialog::OnCancel(wxCommandEvent &event) { EndModal(wxID_CANCEL); } void AudioUnitDialog::OnPreview(wxCommandEvent &event) { mEffect->Preview(); } // Indentation settings for Vim and Emacs and unique identifier for Arch, a // version control system. Please do not modify past this point. // // Local Variables: // c-basic-offset: 3 // indent-tabs-mode: nil // End: // // vim: et sts=3 sw=3 // arch-tag: 21aef079-ec47-4ff9-a359-7d159e2ba0e6