␡
- What You Need
- Accessing the Code
- Using the API
- Connecting It Al
- Testing
- Future Development
- Tip Sheet
Like this article? We recommend
Connecting It All
Now that you have the API tools worked through, you need to begin building your visual plugin. The following code with comments explains how you can do this.
// Windows headers #include <windows.h> #include "iTunesVisualAPI.h" #if TARGET_OS_WIN32 #define GRAPHICS_DEVICE HWND #endif #if TARGET_OS_MAC #define GRAPHICS_DEVICE CGrafPtr #endif #if TARGET_OS_WIN32 #define MAIN iTunesPluginMain #define IMPEXP __declspec(dllexport) #else #define IMPEXP #define MAIN main #endif #define kSampleVisualPluginName "Sample Visual" #define kSampleVisualPluginCreator '\?\?\?\?' #define kSampleVisualPluginMajorVersion 1 #define kSampleVisualPluginMinorVersion 0 #define kSampleVisualPluginReleaseStage 0x80 #define kSampleVisualPluginNonFinalRelease 0 enum { kSettingsDialogResID = 1000, kSettingsDialogOKButton = 1, kSettingsDialogCancelButton, kSettingsDialogCheckBox1, kSettingsDialogCheckBox2, kSettingsDialogCheckBox3 }; struct VisualPluginData { void * appCookie; ITAppProcPtr appProc; #if TARGET_OS_MAC ITFileSpec pluginFileSpec; #endif GRAPHICS_DEVICE destPort; Rect destRect; OptionBits destOptions; UInt32 destBitDepth; RenderVisualData renderData; UInt32 renderTimeStampID; SInt8 waveformData[kVisualMaxDataChannels][kVisualNumWaveformEntries]; UInt8 level[kVisualMaxDataChannels]; /* 0-128 */ ITTrackInfoV1 trackInfo; ITStreamInfoV1 streamInfo; Boolean playing; Boolean padding[3]; /* Plugin-specific data */ #if TARGET_OS_MAC GWorldPtr offscreen; #endif }; typedef struct VisualPluginData VisualPluginData; // ClearMemory // static void ClearMemory (LogicalAddress dest, SInt32 length) { register unsigned char *ptr; ptr = (unsigned char *) dest; if( length > 16 ) { register unsigned long *longPtr; while( ((unsigned long) ptr & 3) != 0 ) { *ptr++ = 0; --length; } longPtr = (unsigned long *) ptr; while( length >= 4 ) { *longPtr++ = 0; length -= 4; } ptr = (unsigned char *) longPtr; } while( --length >= 0 ) { *ptr++ = 0; } } /* ProcessRenderData */ static void ProcessRenderData (VisualPluginData *visualPluginData, const RenderVisualData *renderData) { SInt16 index; SInt32 channel; visualPluginData->level[0] = 0; visualPluginData->level[1] = 0; if (renderData == nil) { ClearMemory(&visualPluginData->renderData, sizeof(visualPluginData->renderData)); return; } visualPluginData->renderData = *renderData; for (channel = 0; channel < kVisualMaxDataChannels; channel++) { for (index = 0; index < kVisualNumWaveformEntries; index++) { SInt8 value; value = renderData->waveformData[channel][index] - 0x80; visualPluginData->waveformData[channel][index] = value; if (value < 0) value = -value; if (value > visualPluginData->level[channel]) visualPluginData->level[channel] = value; } } } #if TARGET_OS_MAC // GetPortCopyBitsBitMap // static BitMap * GetPortCopyBitsBitMap (CGrafPtr port) { BitMap * destBMap; #if OPAQUE_TOOLBOX_STRUCTS PixMapHandle pixMap; pixMap = GetPortPixMap(port); destBMap = (BitMap *)(*pixMap); #else destBMap = (BitMap *)&((GrafPtr)port)->portBits; #endif return destBMap; } #endif /* RenderVisualPort */ static void RenderVisualPort (VisualPluginData *visualPluginData, GRAPHICS_DEVICE destPort, const Rect *destRect, Boolean onlyUpdate) { (void) visualPluginData; (void) onlyUpdate; if (destPort == nil) return; #if TARGET_OS_MAC { BitMap * srcBitMap; BitMap * dstBitMap; Rect srcRect; CGrafPtr oldPort; GDHandle oldDevice; if (visualPluginData->offscreen == nil) return; srcRect = *destRect; MacOffsetRect(&srcRect, -srcRect.left, -srcRect.top); GetGWorld(&oldPort, &oldDevice); if (onlyUpdate == false) { RGBColor foreColor; /* Update our offscreen pixmap */ SetGWorld(visualPluginData->offscreen, nil); foreColor.red = foreColor.green = ((UInt16)visualPluginData->level[1] << 9); foreColor.blue = ((UInt16)visualPluginData->level[0] << 9); RGBForeColor(&foreColor); PaintRect(&srcRect); } srcBitMap = GetPortCopyBitsBitMap(visualPluginData->offscreen); dstBitMap = GetPortCopyBitsBitMap(destPort); SetGWorld(destPort, nil); ForeColor(blackColor); BackColor(whiteColor); CopyBits(srcBitMap, dstBitMap, &srcRect, destRect, srcCopy, nil); SetGWorld(oldPort, oldDevice); } #else { RECT srcRect; HBRUSH hBrush; HDC hdc; srcRect.left = destRect->left; srcRect.top = destRect->top; srcRect.right = destRect->right; srcRect.bottom = destRect->bottom; hdc = GetDC(destPort); hBrush = CreateSolidBrush(RGB((UInt16)visualPluginData->level[1]<<1, (UInt16)visualPluginData->level[1]<<1, (UInt16)visualPluginData->level[0]<<1)); FillRect(hdc, &srcRect, hBrush); DeleteObject(hBrush); ReleaseDC(destPort, hdc); } #endif } /* AllocateVisualData is where you should allocate any information that depends on the port or rect changing (like offscreen GWorlds). */ static OSStatus AllocateVisualData (VisualPluginData *visualPluginData, const Rect *destRect) { OSStatus status; #if TARGET_OS_MAC CGrafPtr oldPort; GDHandle oldDevice; Rect allocateRect; GetGWorld(&oldPort, &oldDevice); allocateRect = *destRect; MacOffsetRect(&allocateRect, -allocateRect.left, -allocateRect.top); status = JRNewGWorld(&visualPluginData->offscreen, 32, &allocateRect, nil, nil, useTempMem); if (status == noErr) { PixMapHandle pix = GetGWorldPixMap(visualPluginData->offscreen); LockPixels(pix); // Offscreen starts out black SetGWorld(visualPluginData->offscreen, nil); ForeColor(blackColor); PaintRect(&allocateRect); } SetGWorld(oldPort, oldDevice); #else (void) visualPluginData; (void) destRect; status = noErr; #endif return status; } /* DeallocateVisualData is where you should deallocate the things you have allocated */ static void DeallocateVisualData (VisualPluginData *visualPluginData) { #if TARGET_OS_MAC if (visualPluginData->offscreen != nil) { JRDisposeGWorld(visualPluginData->offscreen); visualPluginData->offscreen = nil; } #else (void)visualPluginData; #endif } static Boolean RectanglesEqual(const Rect *r1, const Rect *r2) { if ( (r1->left == r2->left) && (r1->top == r2->top) && (r1->right == r2->right) && (r1->bottom == r2->bottom) ) return true; return false; } // ChangeVisualPort // static OSStatus ChangeVisualPort (VisualPluginData *visualPluginData, GRAPHICS_DEVICE destPort, const Rect *destRect) { OSStatus status; Boolean doAllocate; Boolean doDeallocate; status = noErr; doAllocate = false; doDeallocate = false; if (destPort != nil) { if (visualPluginData->destPort != nil) { if (RectanglesEqual(destRect, &visualPluginData->destRect) == false) { doDeallocate = true; doAllocate = true; } } else { doAllocate = true; } } else { doDeallocate = true; } if (doDeallocate) DeallocateVisualData(visualPluginData); if (doAllocate) status = AllocateVisualData(visualPluginData, destRect); if (status != noErr) destPort = nil; visualPluginData->destPort = destPort; if (destRect != nil) visualPluginData->destRect = *destRect; return status; } /* ResetRenderData */ static void ResetRenderData (VisualPluginData *visualPluginData) { ClearMemory(&visualPluginData->renderData, sizeof(visualPluginData->renderData)); ClearMemory(&visualPluginData->waveformData[0][0], sizeof(visualPluginData->waveformData)); visualPluginData->level[0] = 0; visualPluginData->level[1] = 0; } #if TARGET_OS_MAC /* SettingsDialogFilterProc */ static DEFINE_API(Boolean) SettingsDialogFilterProc (DialogPtr theDialog, EventRecord * theEvent, short * itemHit) { Boolean handled; WindowPtr theWindow; VisualPluginData * visualPluginData; GrafPtr savePort; theWindow = GetDialogWindow(theDialog); visualPluginData = (VisualPluginData *) GetWRefCon(theWindow); handled = true; *itemHit = 0; GetPort( &savePort ); MacSetPort( (GrafPtr) GetWindowPort(theWindow) ); switch (theEvent->what) { case nullEvent: { /* Let iTunes have idle events so the visuals can continue while playing */ PlayerHandleMacOSEvent( visualPluginData->appCookie, visualPluginData->appProc, theEvent, nil ); handled = false; break; } case updateEvt: { if( (WindowPtr) theEvent->message == theWindow ) { handled = false; } else { PlayerHandleMacOSEvent( visualPluginData->appCookie, visualPluginData->appProc, theEvent, &handled ); } break; } default: PlayerHandleMacOSEvent( visualPluginData->appCookie, visualPluginData->appProc, theEvent, &handled ); break; } if (handled == false) handled = StdFilterProc( theDialog, theEvent, itemHit ); MacSetPort( savePort ); return handled ; } /* DoSettingsDialog */ static OSStatus DoSettingsDialog (VisualPluginData *visualPluginData) { OSStatus status; DialogPtr theDialog; GrafPtr savePort; status = noErr; GetPort( &savePort ); theDialog = GetNewDialog( kSettingsDialogResID, nil, (WindowRef) -1L ); if( theDialog != nil ) { WindowRef theWindow; SInt16 itemHit; ModalFilterUPP filterUPP; theWindow = GetDialogWindow( theDialog ); MacSetPort( (GrafPtr) GetWindowPort(theWindow) ); SetWRefCon( theWindow, (UInt32) visualPluginData ); SetDialogDefaultItem( theDialog, kSettingsDialogOKButton ); SetDialogCancelItem( theDialog, kSettingsDialogCancelButton ); MacShowWindow( theWindow ); SelectWindow( theWindow ); itemHit = 0; filterUPP = NewModalFilterUPP( SettingsDialogFilterProc ); while( itemHit != kSettingsDialogOKButton && itemHit != kSettingsDialogCancelButton ) { ModalDialog( filterUPP, &itemHit ); switch( itemHit ) { case kSettingsDialogCheckBox1: case kSettingsDialogCheckBox2: case kSettingsDialogCheckBox3: { ControlHandle theControl; if( GetDialogItemAsControl( theDialog, itemHit, &theControl ) == noErr ) { SetControlValue( theControl, GetControlValue(theControl) == 0 ? 1 : 0 ); } break; } case kSettingsDialogCancelButton: status = userCanceledErr; break; default: break; } } DisposeModalFilterUPP( filterUPP ); DisposeDialog( theDialog ); } else { status = memFullErr; } MacSetPort( savePort ); return status; } #endif /* VisualPluginHandler */ static OSStatus VisualPluginHandler (OSType message, VisualPluginMessageInfo *messageInfo, void *refCon) { OSStatus status; VisualPluginData * visualPluginData; visualPluginData = (VisualPluginData *)refCon; status = noErr; switch (message) { /* Sent when the visual plugin is registered. The plugin should do minimal memory allocations here. The resource fork of the plugin is still available. */ case kVisualPluginInitMessage: { visualPluginData = (VisualPluginData *)malloc(sizeof(VisualPluginData)); if (visualPluginData == nil) { status = memFullErr; break; } visualPluginData->appCookie = messageInfo->u.initMessage.appCookie; visualPluginData->appProc = messageInfo->u.initMessage.appProc; /* Remember the file spec of our plugin file. We need this so we can open our resource fork during */ /* the configuration message */ #if TARGET_OS_MAC status = PlayerGetPluginFileSpec( visualPluginData->appCookie, visualPluginData->appProc, &visualPluginData->pluginFileSpec ); #endif messageInfo->u.initMessage.refCon = (void *)visualPluginData; break; } /* Sent when the visual plugin is unloaded */ case kVisualPluginCleanupMessage: if (visualPluginData != nil) free(visualPluginData); break; /* Sent when the visual plugin is enabled. iTunes currently enables all loaded visual plugins. The plugin should not do anything here. */ case kVisualPluginEnableMessage: case kVisualPluginDisableMessage: break; /* Sent if the plugin requests idle messages. Do this by setting the kVisualWantsIdleMessages option in the PlayerRegisterVisualPluginMessage.options field. */ case kVisualPluginIdleMessage: if (visualPluginData->playing == false) RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, false); break; #if TARGET_OS_MAC /* Sent if the plugin requests the ability for the user to configure it. Do this by setting the kVisualWantsConfigure option in the PlayerRegisterVisualPluginMessage.options field. */ case kVisualPluginConfigureMessage: { short oldResFile; short ourResFile; oldResFile = CurResFile(); ourResFile = FSpOpenResFile( &visualPluginData->pluginFileSpec, fsRdPerm ); if( ourResFile > 0 ) { status = DoSettingsDialog( visualPluginData ); CloseResFile( ourResFile ); } else { status = fnfErr; } UseResFile( oldResFile ); break; } #endif /* Sent when iTunes is going to show the visual plugin in a port. At this point, the plugin should allocate any large buffers it needs. */ case kVisualPluginShowWindowMessage: visualPluginData->destOptions = messageInfo->u.showWindowMessage.options; status = ChangeVisualPort( visualPluginData, #if TARGET_OS_WIN32 messageInfo->u.showWindowMessage.window, #endif #if TARGET_OS_MAC messageInfo->u.showWindowMessage.port, #endif &messageInfo->u.showWindowMessage.drawRect); if (status == noErr) RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, true); break; /* Sent when iTunes is no longer displayed. */ case kVisualPluginHideWindowMessage: (void) ChangeVisualPort(visualPluginData, nil, nil); ClearMemory(&visualPluginData->trackInfo, sizeof(visualPluginData->trackInfo)); ClearMemory(&visualPluginData->streamInfo, sizeof(visualPluginData->streamInfo)); break; /* Sent when iTunes needs to change the port or rectangle of the currently displayed visual. */ case kVisualPluginSetWindowMessage: visualPluginData->destOptions = messageInfo->u.setWindowMessage.options; status = ChangeVisualPort( visualPluginData, #if TARGET_OS_WIN32 messageInfo->u.setWindowMessage.window, #endif #if TARGET_OS_MAC messageInfo->u.setWindowMessage.port, #endif &messageInfo->u.setWindowMessage.drawRect); if (status == noErr) RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, true); break; /* Sent for the visual plugin to render a frame. */ case kVisualPluginRenderMessage: visualPluginData->renderTimeStampID = messageInfo->u.renderMessage.timeStampID; ProcessRenderData(visualPluginData, messageInfo->u.renderMessage.renderData); RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, false); break; /* Sent in response to an update event. The visual plugin should update into its remembered port. This will only be sent if the plugin has been previously given a ShowWindow message. */ case kVisualPluginUpdateMessage: RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, true); break; /* Sent when the player starts. */ case kVisualPluginPlayMessage: if (messageInfo->u.playMessage.trackInfo != nil) visualPluginData->trackInfo = *messageInfo->u.playMessage.trackInfo; else ClearMemory(&visualPluginData->trackInfo, sizeof(visualPluginData->trackInfo)); if (messageInfo->u.playMessage.streamInfo != nil) visualPluginData->streamInfo = *messageInfo->u.playMessage.streamInfo; else ClearMemory(&visualPluginData->streamInfo, sizeof(visualPluginData->streamInfo)); visualPluginData->playing = true; break; /* Sent when the player changes the current track information. This is used when the information about a track changes, or when the CD moves onto the next track. The visual plugin should update any displayed information about the currently playing song. */ case kVisualPluginChangeTrackMessage: if (messageInfo->u.changeTrackMessage.trackInfo != nil) visualPluginData->trackInfo = *messageInfo->u.changeTrackMessage.trackInfo; else ClearMemory(&visualPluginData->trackInfo, sizeof(visualPluginData->trackInfo)); if (messageInfo->u.changeTrackMessage.streamInfo != nil) visualPluginData->streamInfo = *messageInfo->u.changeTrackMessage.streamInfo; else ClearMemory(&visualPluginData->streamInfo, sizeof(visualPluginData->streamInfo)); break; /* Sent when the player stops. */ case kVisualPluginStopMessage: visualPluginData->playing = false; ResetRenderData(visualPluginData); RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, true); break; /* Sent when the player changes the track position. */ case kVisualPluginSetPositionMessage: break; /* Sent when the player pauses. iTunes does not currently use pause or unpause. A pause in iTunes is handled by stopping and remembering the position. */ case kVisualPluginPauseMessage: visualPluginData->playing = false; ResetRenderData(visualPluginData); RenderVisualPort(visualPluginData, visualPluginData->destPort, &visualPluginData->destRect, true); break; /* Sent when the player unpauses. iTunes does not currently use pause or unpause. A pause in iTunes is handled by stopping and remembering the position. */ case kVisualPluginUnpauseMessage: visualPluginData->playing = true; break; /* Sent to the plugin in response to a MacOS event. The plugin should return noErr for any event it handles completely, or an error (unimpErr) if iTunes should handle it. */ case kVisualPluginEventMessage: status = unimpErr; break; default: status = unimpErr; break; } return status; } /* RegisterVisualPlugin */ static OSStatus RegisterVisualPlugin (PluginMessageInfo *messageInfo) { OSStatus status; PlayerMessageInfo playerMessageInfo; ClearMemory(&playerMessageInfo.u.registerVisualPluginMessage,sizeof(playerMessageInfo.u.registerVisualPluginMessage)); // copy in name length byte first playerMessageInfo.u.registerVisualPluginMessage.name[0] = lstrlen(kSampleVisualPluginName); // now copy in actual name memcpy(&playerMessageInfo.u.registerVisualPluginMessage.name[1], kSampleVisualPluginName, lstrlen(kSampleVisualPluginName)); SetNumVersion(&playerMessageInfo.u.registerVisualPluginMessage.pluginVersion, kSampleVisualPluginMajorVersion, kSampleVisualPluginMinorVersion, kSampleVisualPluginReleaseStage, kSampleVisualPluginNonFinalRelease); playerMessageInfo.u.registerVisualPluginMessage.options = kVisualWantsIdleMessages | kVisualWantsConfigure; playerMessageInfo.u.registerVisualPluginMessage.handler = VisualPluginHandler; playerMessageInfo.u.registerVisualPluginMessage.registerRefCon = 0; playerMessageInfo.u.registerVisualPluginMessage.creator = kSampleVisualPluginCreator; playerMessageInfo.u.registerVisualPluginMessage.timeBetweenDataInMS = 0xFFFFFFFF; // 16 milliseconds = 1 Tick, 0xFFFFFFFF = Often as possible. playerMessageInfo.u.registerVisualPluginMessage.numWaveformChannels = 2; playerMessageInfo.u.registerVisualPluginMessage.numSpectrumChannels = 2; playerMessageInfo.u.registerVisualPluginMessage.minWidth = 64; playerMessageInfo.u.registerVisualPluginMessage.minHeight = 64; playerMessageInfo.u.registerVisualPluginMessage.maxWidth = 32767; playerMessageInfo.u.registerVisualPluginMessage.maxHeight = 32767; playerMessageInfo.u.registerVisualPluginMessage.minFullScreenBitDepth = 0; playerMessageInfo.u.registerVisualPluginMessage.maxFullScreenBitDepth = 0; playerMessageInfo.u.registerVisualPluginMessage.windowAlignmentInBytes = 0; status = PlayerRegisterVisualPlugin(messageInfo->u.initMessage.appCookie, messageInfo->u.initMessage.appProc,&playerMessageInfo); return status; } /* MAIN */ IMPEXP OSStatus MAIN (OSType message, PluginMessageInfo *messageInfo, void *refCon) { OSStatus status; (void) refCon; switch (message) { case kPluginInitMessage: status = RegisterVisualPlugin(messageInfo); break; case kPluginCleanupMessage: status = noErr; break; default: status = unimpErr; break; } return status; }