Hi,
I am wanting to put together a program that intercepts some of the OS4 signals and allows a user to add sounds to them, such as disk insert, open window, close window, open screen, etc. the way an old Amiga OS classic program called Desktop Magic used to do.
Also, I want the program to be able to attach sounds to whatever programs a user wants to select when they start, that is, if a user decides he wants a certain sound effect when he opens Directory Opus, or any other program of his choosing.
I have the sound player that allows me to use data types to play sounds. Now, what is the proper way to intercept OS4 system signals like open window, insert disk etc?
Thanks!
Okay, here is some preliminary source code for this program (source code).. I am trying to set up a message port to intercept various inuition I/O messages and classes such as IECLASS_CLOSEWINDOW. The sound function properly plays a sound when either Playsnd1() or Playsnd2() is called...but unless I add a delay after the sound the sound object gets disposed of before the sound can be heard.
So Playsnd1() or Playsnd2() works just fine using datatypes to play a sound file that is located in the current directory. However, when I try to trigger a sound file using Playsnd1() or 2 within the MagicEvent handler, it never is heard. I know for example that I am properly comparing the event->ie_Class because it filters out the timer and rawmouse events.
What foolish thing is going wrong here?
Thanks!
You must not do lengthy things in an input handler routine! An input handler is called synchronously in the context of input.device for every input event. While the handler routine is running, every input processing is halted. An input hander must be a very very very very quick routine.
In your input handler routine you should only signal the main program for every event you like to process. The main program can then play the sounds.
It is rather ineffective to load the sound file everytime it should be played. It also means that the sound always comes late. You should load all sounds at the beginning of your program and at an event only execute the play trigger method.
The sound is stopped immediately when you dispose the object. If you want to play the sound until the end you should wait for it to complete.
Hi Thomas,
Yes, I was pretty certain that was a problem...
Can you suggest the best way for me to signal my main program that an event has happened so that it can produce a sound based on the event type? I am just at a loss as to how to proceed from here... should I just pass the control type and any specific code back to the main program from my interrupt handler and let it do all the deciding about what to do?
Also, your sound program that plays files using datatypes...(thank you - it is an excellent example), but how do i get it to know when the sound clip finished? Some sounds may be long and some short. right now i am simply adding a delay before disposing of the object, but I need to customize the delay and tailor it to the length of each sound unless I can determine when the sound clip is done playing. Is there a way to do that?
Thank you again for your knowledge and sharing you insights about Amiga OS4 coding!
Either use Signal() or PutMsg() depending on your purpose.
I am not sure which example you mean. If you look at this one, it does exactly that: http://thomas-rapp.homepage.t-online.de/examples/dtplay.c
But actually this is not your problem. Your problem is that you dispose the object immediately. IMHO you should load the sounds files in the beginning of your program and keep them in memory all time and just trigger the method when an event happens.
Here is a complete example: http://thomas-rapp.homepage.t-online.de/examples/keybeep.lha
Yes, dtplay.c was the example I was using previously. Thank you very much for the keybeep example! That is exactly what I wanted to do!
I have added cases to the switch for ie_Class such as IECLASS_DISKINSERTED and IECLASS_DISKREMOVED that work just fine. However, when I added cases for IECLASS_REQUESTER, IECLASS_ACTIVEWINDOW and IECLASS_CLOSEWINDOW they are not working. What am I not understanding about this messaging? (note - I have added several more sounds so my numbers such as 7, 8 and 9 are valid sounds).
case IECLASS_REQUESTER:
if (ie->ie_Code == IECODE_REQSET)
send_msg (handler->port,9);
break;
case IECLASS_ACTIVEWINDOW:
send_msg (handler->port,8);
break;
case IECLASS_CLOSEWINDOW:
send_msg (handler->port,7);
break;
Won't these work just like IECLASS_DISKINSERTED and trigger when, for example, any window is closed? Looking at the autodocs, I see no refeence to IECLASS_CLOSEWINDOW. Headers just show me the value of this define, which has the comment "User has selected the active window's close button". Does this only apply to the active window where the handler program was run, or any active window?
Thanks,
Scott
It's probably about the priority of your input handler. If you want to catch raw input events (like rawkey or rawmouse) your priority has to be higher than Intuition's handler because that one filters out all events it handles. OTOH if you want to catch events which are created by Intutition (like requester or window related) your priority probably needs to be lower than that of the Intuition handler.
Okay, with the default priority value of 80 for the input handler, I see ie_Class values of:
(note - number are decimal not hex values)
1 RAWKEY
2 RAWMOUSE
6 TIMER
15 DISKREMOVED
16 DISKINSERTED
23 MOUSEWHEEL
24 EXTENDED RAWKEY
When I change the priority to 40, 10 or even 1 I also see:
3 EVENT
8 GADGETUP
17 ACTIVEWINDOW
18 INACTIVEWINDOW
21 CHANGEWINDOW
Seems like no matter what I set the priority to though, I never see any other event Class types, such as REQUESTER, MENULIST or CLOSEWINDOW. I try opening and closing windows for example and never see a CLOSEWINDOW Class event, or I try opening a shell and typing dir xyz: to get a requester to appear, but never see the Class for REQUESTER.
Do these things need something else to occur? Do I need to point to a particular window or screen structure somewhere for it to know these events are occuring?
Thanks!
I am quite sure that "requester" does not refer to requester windows but to real Intuition requesters (which are quite unpopular and not often used).
I could also imagine that some of the events are internal to intuition.library or are write-only. For example you can close a window by sending an IECLASS_CLOSEWINDOW event using IND_WRITEEVENT. Intuition will then send an IDCMP_CLOSEWINDOW to the specified window.
Intuition itself probably does not send an input event to the chain when a user clicks on a close gadget because there is nobody else but Intuition who needs this information (nobody but Intuition knows what a window is).
I think I already told you on another forum that you might need to patch system functions in order to get some of the desired information.
Yes, thank you again for your insights.
In order to handle both low priority and high priority class types, I have made a copy of the input handler and handler_on functions (renaming them of course), which sets two sets of input handlers in the main function, one with a priority of 81 and the other with a priority of 10. I use the same message port for both handlers, the same sound loading and messaging functions etc but I am now able to properly process both low and high priority based classes in one program. This allows me access to almost everything I wanted to add sounds to. I am sure to close both handlers when the program ends.
If I want to add sounds to such things as opening screens, etc I will indeed have to patch the OS, which I prefer not to do, at least not for now.
Thank you again for your insights in Amiga OS programming! You have been tremendously helpful.
You shouldn't copy the entire handler_on and _off functions. You need to open input.device only once and can then send multiple commands to it. You should rather put an inter_high and inter_low into the struct handler instead of the single inter. In the handler_on function intialize both Interrupt structures and call IND_ADDHANDLER twice, once for each inter_xxx. Similarly in the handler_off function call IND_REMHANDLER twice with pointers to each inter_xxx.
@Thomas
Very good. Yes, that works fine as you suggested.
Now, the next question....
Is there a way to intercept the name of any program that is started from workbench? I would like to allow the user to attach a sound to any program they select so that the sound is made when that program starts. So for example if a program named "DPaint" is started, no matter where it is started from, a particular sound will be made when it starts.
Thank you!
For this you probably have to hack into IDOS->CreateNewProcTagList (for Workbench) or IDOS->RunCommand (for Shell/CLI).
Use IDOS->NotifyProcListChange() to get notified when the process list changes. Then use IDOS->ProcessScan() function to copy the list of running processes. Keep your old list and compare it to the new one to find out what processes were started.
I notice that SDTA_Volume controls the volume of a datatype sound file being played, but it does not change the volume of a .mp3 sound being played. Is there some other way to control the volume of the sound playback of an MP3 datatype sound?
Thanks!
@TSK
Okay, I added the NotifyProcListChange to my main loop and it works correctly, like this:
if ((mainport = init_mainport (appname)))
{
LoadPrefs();
load_sounds(sounds);
if ((handler = handler_on (mainport)))
{
if ((handler2 = handler_on2(mainport)))
{
if( ! NotifyProcListChange(NULL, SIGBREAKB_CTRL_E, 0))
{
PrintFault(IoErr(), NULL);
handler_off (handler);
handler_off (handler2);
free_sounds (sounds);
delete_mainport (mainport);
for (i=0;i < NUM_SOUNDS;i++)
{
free(sound_names[i]);
}
return(RETURN_ERROR); // fix this later!
}
do {
signals = Wait((1L << mainport->mp_SigBit) | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_C );
if( signals & SIGBREAKF_CTRL_E ) /* Received ^E signal */
{
PutStr("Process list change signal received !!\n");
}
else if (1L << mainport->mp_SigBit)
{
process_msgs(mainport,sounds);
}
}
while (!(signals & SIGBREAKF_CTRL_C));
handler_off (handler);
}
handler_off (handler2);
}
NotifyProcListChange(NULL, NPLC_END, 0);
free_sounds (sounds);
delete_mainport (mainport);
}
for (i=0;i < NUM_SOUNDS;i++)
{
free(sound_names[i]);
}
Now, I do not understand how to set up a hook to make the ProcessScan function work. I see the example in the SDK dos.doc file, but it is designed to specifically identify a child process and looks only for one instance. You say that I can obtain a list from the ProcessScan call (or a pointer to a list, I'm sure). Can you show me an example of this or where I can find one?
Thanks!
@TSK
Thank you. Your example is perfect for what I want to do!
I have modified my main program to add the call to the processhookfunc and ProcessScan(ProcHook) and have gotten everything to work well.
Instead of using your function to generate a complete list of all running tasks (done by passing an argument of 0), what I do is use the first occurance of a NotifyProcListChange to generate a table of booleans that indicate which of the list of programs I have in my array are currently in the process list. I have an array of booleans named "running[]" that tracks which of my program names is running when my main programs first starts. Then, whenever I get a new NotifyProcListChange, I check the table of programs with the table of booleans that shows what is already running from my list of programs, and when it shows that it was not already running, it performs and action (playing a sound) and then sets the running boolean true for the running array position that corresponds to the program name in my names array.
That all works fine, and I have set it up to signal when a program fom my program list is now running when it wasn't previously running when I am notified of a ProcListChange.
But now I am concerned that if I add the playing of a sound to my main loop, things will become too time consuming. I do not want to have too much going on while I am responding to the mainport mp_sigbit for CTRLE.....I need to figure out how to have a sound get "kicked off" without taking any main loop time.
Scott
@AmigaOneFan
You could always spawn a separate sound player process and use its message port to signal it that you want to play a sound. If you use the message mechanism you could even tell it what specific sound to play (dont forget to provide some kind of "die, ba*****!" aka "quit" message).
There is quite comprehensive documentation about CreateNewProcTags() and how to signal the new process. I did it for InsanePlaya and it was dead easy ;)
Coder Insane-Software
www.insane-software.de
If you use the DTM_TRIGGER method, the sound is played in the background anyway. You don't need to create a new process.
@Thomas
Yes, the DTM_Trigger method works great! I am not seeing any issues with setting background sounds to play when triggered by the launch of new programs.
Thanks!
On a totally different subject....
Should i make my sound patching program a commodity? It is set up already so that only a single instance of it runs, and running it again triggers the first instance to turn off. What advantage might there be to making my program a commodity? Any advantage at all?
Thanks
@AmigaOneFan
A commodity is the modern way for a lot of the thimgs you want to do. It goes on the Exhange list and can be activated and deactivated. You can also set it up so you get notified when your program is run again so you may want to quit it. A main feature of the commodity.library is being able to intercept the input stream and add to it. You can set up Cx filters and Cx handlers in a more high level fashion than an ordinary input handler.
Sure an input handler will still work but this method is more low level and old fashioned. I suppose it could be like programming Intuition gadgets instead of using GadTools. Or creating and sending customised DOS packets directly in Exec instead of using dedicated DOS functions. It works, but we aren't stuck in OS1.3 times anymore.
Still, it does require learning about Cx, and its quirks. But I think it is worth it. And if you end up making a commodity anyway it could save you some work. :-)
@Hypex
well, I already have my program set up to do any of these things i needed....perhaps I may try to make it a commodity later though just to see how it is done. Is there any sort of tutorial or example of how to create a commodity program?
Thanks!