| Register | FAQ | Calendar | Search | Today's Posts | Mark Forums Read |
|
#1
| |||
| |||
| For a hobbyist project, I'm writing a music composer, and I really want to make it object-oriented because the data really lends itself to OOP for reasons I'll explain later. The only problem is that, while I'm a competent Pascal programmer, I've never done OOP before. My environment is Turbo Pascal 7.0. Before I lay out the details, I want to stress this is not a homework assignment :-) I am not asking for code, just the normal, best-practices way to do what I want to accomplish. I thought of creating a "song" object with the ability to enter notes, save/load the song data, etc because it makes perfect sense; the methods can validate the data and generally abstract the song. But the song needs to play, and that's where I get stuck on the proper way to program in OOP to access the song data. Should I: - Make a new "player" object that inherits the song object (so that it can directly reference the song data structures)? - Make a new "player" object that calls the song object's methods to pass the data back and forth? - Have the song object pass a pointer to the note data to be accessed by the player object? (that seems like it's defeating the whole purpose of OOP) - Build the playback routines directly into the song object, so that I have a single object with a gazillion methods? The program needs to be able to play the song, not only for one particular output device, but several. That means the playback routines need to be VIRTUAL so that I can replace them with methods for additional sound devices as I code them. It is that requirement that has me confused about the proper direction to take. I was leaning toward this: A song object; a player object that inherits the song object and contains only generic VIRTUAL playback methods; a device-specific object that replaces the virtual methods with methods for that device's hardware. I figure that way I can get direct access to the song's data structures without "exposing them to the outside", and I can keep building on previous code as necessary. Does that sound like the right way to go, or am I barking up the wrong tree? |
|
#2
| |||
| |||
| ----- Original Message ----- From: "Jim Leonard" <MobyGamer@gmail.com> > Should I: > > - Make a new "player" object that inherits the song object (so that it > can directly reference the song data structures)? > - Make a new "player" object that calls the song object's methods to > pass the data back and forth? > - Have the song object pass a pointer to the note data to be accessed > by the player object? (that seems like it's defeating the whole > purpose of OOP) > - Build the playback routines directly into the song object, so that I > have a single object with a gazillion methods? You should definately have different objects for the player and the song because: (1) The song object encapsulates a data element -TSong. (2) The Playback object represents [an interface to] a device driver - TPlayer. The TPlayer should be an abstract object so that, as you say, you can virtualize the player details. TSong might also want to be an abstract type as well, so that different song types can be played by the same player type. Now you have to decide what the interface ("contract") between the TSong and the TPlayer is going to be. Ideally, only one of them at most should "know" about the other. It might be better for both to be ignorant of each other, and use a third object to transport the data from the TSong to the TPlayer. Some sort of stream object comes mind for this role. > I was leaning toward this: > A song object; Yes. > a player object that inherits the song object and contains only > generic VIRTUAL playback methods; I wouldn't. Use a different object for the player. Ah, I think you don't mean "inherits", but rather "contains". Yes, perhaps. But then you have a 1:1 relationship between Song and Player, so I would probalbly do it use a stream object as described above. > a device-specific object that replaces the virtual methods with > methods for that device's hardware. Yes. > I figure that way I can get direct access to the song's data > structures without "exposing them to the outside", and I can keep > building on previous code as necessary. You could also use a procedural "data-getter" method of TSong that retrieves data from the Song and stores it in a buffer of the TPlayer. The TSong (or a 3rd party) would pass the function to the TPlayer, which would then call it whenever it needs "feeding". Jay -- Jason Burgon - author of Graphic Vision http://homepage.ntlworld.com/gvision |
|
#3
| |||
| |||
| Jim Leonard schreef: > For a hobbyist project, I'm writing a music composer, and I really > want to make it object-oriented because the data really lends itself > to OOP for reasons I'll explain later. The only problem is that, > while I'm a competent Pascal programmer, I've never done OOP before. > My environment is Turbo Pascal 7.0. Before I lay out the details, I > want to stress this is not a homework assignment :-) I am not asking > for code, just the normal, best-practices way to do what I want to > accomplish. > > I thought of creating a "song" object with the ability to enter notes, > save/load the song data, etc because it makes perfect sense; the > methods can validate the data and generally abstract the song. But > the song needs to play, and that's where I get stuck on the proper way > to program in OOP to access the song data. Should I: > > - Make a new "player" object that inherits the song object (so that it > can directly reference the song data structures)? > - Make a new "player" object that calls the song object's methods to > pass the data back and forth? > - Have the song object pass a pointer to the note data to be accessed > by the player object? (that seems like it's defeating the whole > purpose of OOP) > - Build the playback routines directly into the song object, so that I > have a single object with a gazillion methods? > > The program needs to be able to play the song, not only for one > particular output device, but several. That means the playback > routines need to be VIRTUAL so that I can replace them with methods > for additional sound devices as I code them. It is that requirement > that has me confused about the proper direction to take. > > I was leaning toward this: > A song object; > a player object that inherits the song object and contains only > generic VIRTUAL playback methods; > a device-specific object that replaces the virtual methods with > methods for that device's hardware. > I figure that way I can get direct access to the song's data > structures without "exposing them to the outside", and I can keep > building on previous code as necessary. > > Does that sound like the right way to go, or am I barking up the wrong > tree? Interesting project. Controlling the PC sound possibilities in TP or BP is not easy. In don't know how you would organise this. Perhaps Delphi will give you more possibilities here. But you are very right that this kind of program requires OOP. If you can get your hands on a copy of Borland Pascal with objects V7.01 I would strongly recommend this instead of TP7. Try to find a legal copy, but for educational projects no one will care if you try to find the BP.Zip installation file somewhere Inheritance is very handy if there are several enteties that all need to be treated more or less in the same way. E.g. A note and a tone have the parameter "duration" in common, but a note has the added parameters heigth, volume, rampup and rampdown etc. So it is logical that TNote is a descendant of TTone, which in itself descends from Tobject. Both will have a play method that can be used, but TTone will override the Play method of TNote. Next is to define the Melody object, which can best be a descendant of Tcollection. The collection can be filled with objects either being note or tone. The common factor between violin, piano and drum is that they are all different kinds of musical instruments. So if you want to load them in a player there should be an inherited common method e.g. Play. type PInstrument:^TInstrument; TInstrument bject(tobject)melody:tmelody; constructor init: destructor done; procedure play; virtual; end; implementation constructor TInstrument.init; begin melody.init; end; Procedure TInstrument.play; begin abstract; {Always to be overridden by a descendant} end; destructor TInstrument.done begin melody.done; end; The abstract statement will generate an error on execution. This procedure should always be overwritten by the actual instruments of piano, violin and drum which are descendants of TInstrument. Each of them holds different sound samples and different ways on how to connect the tone and duration to the sound sample. The player will probably have a list of instruments that can all play simulataneously. In order to call a method of an instrument of unknown implementation, you can simply call the abstract method of the parent. Something like Procedure tplayer.play procedure plee(p instrument);far;begin p^.play; end; begin foreach(@plee); end: But the puzzle is entirely yours. There are more ways to do what you want. Good luck -- Femme |
|
#4
| |||
| |||
| On Feb 5, 1:17 pm, "Jason Burgon" <jayn...@ntlworld.com> wrote: > You should definately have different objects for the player and the song > because: > > (1) The song object encapsulates a data element -TSong. > (2) The Playback object represents [an interface to] a device driver - > TPlayer. Actually, there are basic song handling routines that are common to playing *any* song, such as "slide this note upward each tick for a portamento", etc. The device-specific routines would come later, so that's why I was leaning toward something like: song = object player = object(song) playSpk = object(player) playAdlib = object(player) playGMIDI = object(player) That sort of thing. If you're familiar with PC MOD players/trackers, that's the same idea; there are song maintenance operations to be done on every interrupt tick, but those operations are independent of the actual output device used. > TSong might also want to be an abstract type > as well, so that different song types can be played by the same player type. While that's a good design decision, it is quite out of scope for this project The goal was to create an editor/player for very low-resource machines, so the song data format is actually a big part of the design -- I'll explain later in this post. > Now you have to decide what the interface ("contract") between the TSong and > the TPlayer is going to be. Ideally, only one of them at most should "know" > about the other. It might be better for both to be ignorant of each other, > and use a third object to transport the data from the TSong to the TPlayer. > Some sort of stream object comes mind for this role. If I didn't care about performance, I would agree this is the most proper way to do it. However, the target platform for this project is -- don't get mad -- an original IBM PC, which is a 4.77MHz 8088. (Most people hit *DELETE* after reading that, but I like challenges in my hobby...) Not only that, but the music must play at the same time many other things are going on, so player performance is extremely important. It's important enough that I started this project with the song data format, because I wanted to be able to retrieve a note in a single data access (if you're curious, each note+effects is only 2 bytes, which I plan to fetch with LODSW and then do all processing in registers). This is why I was trying to figure out the most efficient way to pass data between objects -- ideally, there would be no passing at all (ie. the generic player object would be able to read the song data directory without passing it on the stack or through a stream). So, with this in mind, do you think my idea (see above) is the best compromise? |
|
#5
| |||
| |||
| On Feb 5, 3:01 pm, Femme Verbeek <fv2...@nospam.tcenl.com> wrote: > If you can get your hands on a copy of Borland Pascal with objects V7.01 > I would strongly recommend this instead of TP7. Try to find a legal > copy, but for educational projects no one will care if you try to find > the BP.Zip installation file somewhere I have owned BP7 on CDROM since the day it came out, but I must use TP7 because of my target platform, which is... let's just say, very small and slow :-) See my reply to Jason for more details on the target platform, which is a driving factor in the design. > E.g. A note and a tone have the parameter "duration" in common, but a > note has the added parameters heigth, volume, rampup and rampdown etc. > So it is logical that TNote is a descendant of TTone, which in itself > descends from Tobject. Both will have a play method that can be used, > but TTone will override the Play method of TNote. I hadn't thought of breaking it down that granularly -- that's an interesting idea, I'll have to think about that. > Next is to define the Melody object, which can best be a descendant of > Tcollection. The collection can be filled with objects either being note > or tone. I had definitely not thought about that at all :-) It's an interesting idea, but unfortunately memory and speed are at a premium on my target platform, and unless I'm terribly mistaken I don't think tcollection is the fastest thing I can use. Thanks for your thoughts. |
|
#6
| |||
| |||
| Jim Leonard schreef: > On Feb 5, 3:01 pm, Femme Verbeek <fv2...@nospam.tcenl.com> wrote: >> If you can get your hands on a copy of Borland Pascal with objects V7.01 >> I would strongly recommend this instead of TP7. Try to find a legal >> copy, but for educational projects no one will care if you try to find >> the BP.Zip installation file somewhere > > I have owned BP7 on CDROM since the day it came out, but I must use > TP7 because of my target platform, which is... let's just say, very > small and slow :-) See my reply to Jason for more details on the > target platform, which is a driving factor in the design. In my experience you'll run quite quickly into the limits of the TP IDE, whereas BP real mode still gioing strong. The executable will not differ in size. I made a complete 3d cad-cam system in BP, which compiles to less than 400kB. I can still compile and run in real mode BP, which is handy for using the built-in debugger, but not in TP7. > >> E.g. A note and a tone have the parameter "duration" in common, but a >> note has the added parameters heigth, volume, rampup and rampdown etc. >> So it is logical that TNote is a descendant of TTone, which in itself >> descends from Tobject. Both will have a play method that can be used, >> but TTone will override the Play method of TNote. > > I hadn't thought of breaking it down that granularly -- that's an > interesting idea, I'll have to think about that. > The advantage is of course that you can write clear code. Each object handles its own data, like saving and restoring. >> Next is to define the Melody object, which can best be a descendant of >> Tcollection. The collection can be filled with objects either being note >> or tone. > > I had definitely not thought about that at all :-) It's an > interesting idea, but unfortunately memory and speed are at a premium > on my target platform, and unless I'm terribly mistaken I don't think > tcollection is the fastest thing I can use. > Somehow the memory management of the OOP methods is very well optimised, at least I never noticed any difference. If you use a lot of data it is actually faster than conventional methods using arrays. -- Femme |
|
#7
| |||
| |||
| On Feb 6, 7:02 pm, Femme Verbeek <fv2...@nospam.tcenl.com> wrote: > In my experience you'll run quite quickly into the limits of the TP IDE, > whereas BP real mode still gioing strong. The executable will not differ > in size. If it ever becomes a problem, I can edit in my own editor and then just compile command-line :-) My fear is that, if I don't write the code on the target platform itself, I may get sloppy or wasteful. Because compiling takes so long on such a platform, I find myself thinking about my code and reviewing it much more than when I first started out on a 386 (which is 8x the speed of what I'm coding this for). > > I hadn't thought of breaking it down that granularly -- that's an > > interesting idea, I'll have to think about that. > > The advantage is of course that you can write clear code. Each object > handles its own data, like saving and restoring. That is already the goal, but you had broken it down to the individual instrument :-) > > I had definitely not thought about that at all :-) It's an > > interesting idea, but unfortunately memory and speed are at a premium > > on my target platform, and unless I'm terribly mistaken I don't think > > tcollection is the fastest thing I can use. > > Somehow the memory management of the OOP methods is very well optimised, > at least I never noticed any difference. If you use a lot of data it is > actually faster than conventional methods using arrays. Since an array access is essentially MOV AX,[SI+BX] I can't possibly see how it would be faster... I am envious of EMS streams, though :-) |
|
#8
| |||
| |||
| Interesting project. What sound card do you intend to use with the 4.77 MHz 8088 machine? |
|
#9
| |||
| |||
| On Feb 7, 7:10 am, dik <quag...@yahoo.com> wrote: > Interesting project. What sound card do you intend to use > with the 4.77 MHz 8088 machine? Initially, none. I will be using the built-in speaker, first with multiplexing (arpeggios) and later with a simple state machine to mix multiple square waves realtime. I am dying to know how Tim Follin produced his stuff on the ZX Spectrum... up to five voices, with multiple instrument timbres... |
|
#10
| |||
| |||
| On Feb 7, 10:39 am, Jim Leonard <MobyGa...@gmail.com> wrote: > Initially, none. I will be using the built-in speaker, first > with multiplexing (arpeggios) and later with a simple state > machine to mix multiple square waves realtime. I have always wondered how this is done. What is the user interface you envision? A Csound-like pre-processor? Some kind of a player piano? A virtual keyboard? Thanks - DIK |
![]() |
| Thread Tools | |
| Display Modes | |
In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.