Development: Tutorials

Tutorial: Writing a Simple Music Player with Pragmatic Smalltalk

Let's make a simple music player that demonstrates using Gorm and Smalltalk together: Music Player screenshot

Clicking "Play Files" will open a file browser where you can choose some songs to play. They will start to play when you press "OK", and you will be able to use the slider to seek to a different position in the current song, or press "Skip" to go to the next song in the queue.

Step 1: Gorm

If you are new to Gorm, there are a few tutorials which cover it. Here is one, and another one which is a bit older.

Start a new application in Gorm, and from the Classes pane, create a new NSObject subclass called MusicController with the following outlets/actions:

  • MusicController
    • Outlets
      • positionSlider
    • Actions
      • setPosition:
      • playFiles:
      • skip:

Instantiate the MusicController class, and add the "Skip" and "Play Files" button and a horizontal slider to the main window.

Make the following connections:

  • MusicController's positionSlider outlet to the horizontal slider
  • The horizontal slider to MusicController's setPosition: action
  • The "Play Files" button to MusicController's playFiles: action
  • The "Skip" button to MusicController's skip: action

Save the finished gorm file as MusicApp.gorm.

Here is a diagram showing the connections between the UI and the MusicController object:

Connections diagram

When our application is running, clicking on "Skip" will send the skip: message to the MusicController instance. Also, the positionSlider instance variable will be set to the horizontal slider object (an NSSlider.)

Next, let's write the implementation of MusicController.

Step 2: Smalltalk

MusicController's role is to respond to the messaeges sent from the UI (playFiles:, skip:, setPosition:). It displays the file dialog in respons to playFile:, uses MediaKit to actually play the songs, and also runs a timer which checks the song position using MediaKit and updates the position of the slider.

MKMusicPlayer is the public interface of MediaKit. inNewThread is a method in EtoileThread's category on NSObject.

The map: method in NSArray, provided a single-paramater block, returns a new array derived from the receiver by "applying" the block to each element. Here, we use it to convert an array of NSStrings representing filenames to an array of file:// NSURLs.

MusicController.st


NSObject subclass: MusicController [
    | player positionSlider |

awakeFromNib [
    player := MKMusicPlayer alloc initWithDefaultDevice inNewThread.

    NSTimer scheduledTimerWithTimeInterval: 1
                                    target: self
                                  selector: 'timerEvent:'
                                  userInfo: nil
                                   repeats: 1.
]
timerEvent: sender [
            positionSlider setMaxValue: player duration.
            positionSlider setIntValue: player currentPosition.
]
setPosition: sender [
            player seekTo: positionSlider intValue.
    ]

playFiles: sender [
| openPanel |
    openPanel := NSOpenPanel openPanel.
            openPanel setCanChooseDirectories: 0.
    openPanel setAllowsMultipleSelection: 1.
            openPanel runModalForTypes: {'mp3'. 'ogg'. 'aac'. 'm4a'}.
    player setQueue:
        (openPanel filenames map: [ :path | NSURL fileURLWithPath: path ]).

    player play.
]

skip: sender [
    player next.
]

]

Step 3: Objective-C Main Method and Makefile

main.m


#import <Foundation/Foundation.h>
#import <SmalltalkKit/SmalltalkKit.h>

int main(int argc, char **argv) { [SmalltalkCompiler loadAllScriptsForApplication]; NSLog(@"Compiled smalltalk scripts."); return NSApplicationMain(argc, (const char **) argv); }

[SmalltalkCompiler loadAllScriptsForApplication] compiles all .st files in the application bundle. The Smalltalk classes can then be used as normal Objective-C classes (though if you wanted to instantiate one in ObjC, you need to use something like [[NSClassFromString(@"MusicController") alloc] init] since there's no header file.)

GNUmakefile


include $(GNUSTEP_MAKEFILES)/common.make

APP_NAME = MusicApp $(APP_NAME)_OBJC_FILES = $(wildcard *.m) $(APP_NAME)_MAIN_MODEL_FILE = $(APP_NAME).gorm $(APP_NAME)_RESOURCE_FILES = $(APP_NAME).gorm $(wildcard *.st) $(APP_NAME)_GUI_LIBS = -lMediaKit -lSmalltalkKit -lSmalltalkSupport -lLanguageKit

include $(GNUSTEP_MAKEFILES)/application.make

  • MediaKit is our media framework. It uses the FFmpeg project's libavcodec/libavformat and OSS for sound output.
  • LanguageKit is the core of Pragmatic Smalltalk, but is designed to be usefult for writing compilers for other languages as well.
  • SmalltalkKit adds the Smalltalk-specific bits to LanguageKit (Smalltalk parser, etc.)
  • SmalltalkSupport contains supporting code for Smalltalk, such as the NSArray map: method, and BigInt support using GMP.

Now, make sure you have MusicController.st, main.m, and GNUmakefile in the current directory and run:


make
# or gmake on FreeBSD

./MusicApp.app/MusicApp

Troubleshooting

If the position slider doesn't move when a song should be playing, make sure that you made all four connections in Gorm properly, and didn't misspell any outlet or action names.

Try different media files if it still doesn't work.

If the slider moves on its own but you don't hear any sound, check that other OSS applications work. In Linux, only one process can use ALSA's OSS emulation at a time, so make sure nothing else that uses OSS is running.