This code creates a dll that is called by an executable (originally
written in C) to allow real-time listening to an DSP-based audio
processor written in Fortran. This is prototype code for algorithm
development; the algorithms will eventually be ported to dedicated DSP
chips and written in assembler.
At the moment, the basic structure of the Fortran code is as follows:
Module Common_data
Integer::Controls(8)
end module Common_Data
Program Audio_Processor
USE Common_data
[declarations for left channel variables]
[declarations for right channel variables]
[calling program loads values into Controls(1:8)]
[calling program loads audio_buffer_in_left, audio_buffer_in_right]
[calling program reads audio_buffer_out_left, audio_buffer_out_right]
do i=1,n_blocks
call Processor(dummy left-channel variables,i)
call Processor(dummy right-channel variables,i)
end do
end program Audio_Processor
subroutine Processor(dummy variables,initialize)
USE Common_data
Implicit None
[declaration of variables; all arrays are statically allocated]
SAVE (some variables)
If(initialize==1) THEN
set some variable values to zero
End If
some executable code
end subroutine Processor
subroutine a1(variables)
declarations
SAVE(some variables)
body of subroutine
end subroutine a1
.
.
.
subroutine an(variables)
declarations
SAVE(some variables)
body of subroutine
end subroutine an
===============
At the moment, the program executes a block of code for the left channel
and then executes the same block for the right channel. Each time a new
block is executed, SAVED variables are zeroed out. The calling program
does some crossfades at the beginning and end of the output of Processor
to make up for the fact that the zeroing of variables causes
discontinuities.
Subroutines a1 through an are never called by the main program, but some
of these subroutines call others. For example, a1 might call a2 and a3.
Typically, subroutines a1...an are called (inside of
subroutine Processor) once per audio sample, whereas the audio blocks
handled by subroutine Processor contain 2**15 samples. So interprocess
optimization is quite important to minimize calling overhead.
My problem is this: Right now, the program is running in one core of my
computer and is right at the point where I am starting to get gaps in
the audio because the program needs more cycles than one core can
deliver.
The most obvious thing to do is to use IVF's support of parallel
multithreading to force the left and right channels to be computed in
parallel on the two cores. However, this means that the memory space for
the left and right channels has to be made distinct, whereas at the
moment the memory is being shared. This is possible only because the
left and right channel blocks are computed sequentially.
The most obvious way around this issue is to copy the subroutines
Processor and a1 through an and to rename them. This will probably work,
but is somewhat error-prone.
Another possiblity is to copy the file containing the Processor
subroutine and subroutines a1...an, but only rename the Processor
subroutine. This leads me to my questions. (I have always found the
scoping rules of post-F77 confusing and hard to remember, so I thought
it would be better to ask the experts.)
First -- If I put subroutines a1...an in a Contains statement inside
subroutine Processor, can those names (a1...an) be reused inside
subroutine Processor_Right_Channel without causing naming conflicts?
something like:
Subroutine Processor_Left_Channel
USE Common_data
[body of subroutine]
Contains
subroutine a1()
end subroutine a1
.
.
.
subroutine an()
end subroutine an
end subroutine Processor_Left_Channel
Subroutine Processor_Right_Channel
USE Common_data
[body of subroutine]
Contains
subroutine a1()
end subroutine a1
.
.
.
subroutine an()
end subroutine an
end subroutine Processor_Right_Channel
Second -- Can I use Contains if some of the subroutines in the Contains
block call other subroutines in the same Contains block? In other words,
can you nest subroutines within a given Contains block?
I know I probably should have used modules to encapsulate the
subroutines and force explicit interfaces. However, the code runs OK now
and the interfaces between the subroutines are quite simple, so I think
the interfaces are all correct.
Another thing to know is that all arrays inside the subroutines are
statically allocated for the sake of execution speed, which is crucial
in this application. This is the main reason why I wrote the code in as
straightforward a way as possible, hoping that this would allow maximum
optimization. Another reason to be straightforward is that I have no
good way to debug the code because it has to be compiled with full
optimization in order to execute fast enough. I have also turned on
IVF's automatic vectoization for the same reason.
Finally, I used Implicit None in all routines, so all variables are
explicitly declared. (If I understand the standard correctly, explicitly
declaring a variable inside an internal procedure breaks the connection
between it and an identically named variable in the calling procesure,
making it private to the internal procedure.)
So my main question is this:
Should I just duplicate all the subroutines, rename the duplicates, and
adjust the calls accordingly in the right channel code
or
can I use the "contains" trick to reduce the amount of renaming
necessary
or
is there another, better way of solving the problem that does not
compromise the execution speed of the code by trying to get too fancy?
It would also be nice if I could change the code in just one place and
have the changes show up in both the right and left channels. I intend
to use Include for this unless there is a good reason not to.
Thanks in advance for any advice.
I was going to say that more information is needed, but maybe not.
Since you process both left and right channel, it would seem easiest
to separate those, such that one does left and one right.
Exactly what you need to do to do that isn't so obvious,
but I think it should work pretty well.
-- glen