posted
These are the development and release notes for synSpace: Drone Runners, version 1.0.07
Summary:
* Improved TRAX editor (edits notes of groove) * Improved synthesizer sampling * Pretty massive changes to synth (adds FM synth) * Improved FILTER module in synth (two LFO now) * Expanded synth to have detail page for each module * re-did synth NOISE module * vocoder samples can now be used as synth oscillator waveforms * oscillator 9 can use 'live vocoder' as waveform * added option to feed synth output back into vocoder * added the rhythm rainbow to vocoder (shows notes, chords, keysig, and bpm estimates for current vocoding) * added "Chording Arpeggiator" with programmable arpeggiation patterns * added key signature support for cool music stuff * enhanced sequencer LOOP editing. * added a first cut 'percussion detector' for the vocoder in music mode -- not very good yet. * Oscillators may now be individually de-tuned, can be assigned several different waveforms (including vocoder samples) and have a distortion setting. * reworked vocoder to be very focused on Harmonics (my new friends) * switched to a ROYGBIV color scheme to convey 'seven values' (like the seven roman numeral chords) and individual notes in the keysig (CDEFGAB) * added a +/- 9dB preAmp to vocoder * Added a 'Mouth' simulator for playing back formant=driven speech (but intended to carry the emotions without the words -- Charley Brown Adult Voice) Baby step towards 'in game acoustic emotion sharing without danger of profanity') * You can now import starmaps either from your Google Drive, or directly from a file you drag to your device over USB. * adds support for 16 levels of 'note velocity' (loudness), plumbed through the synth, sequencer, and vocoder. * for Video Documentation of features, please search for "Samsyn2" on YouTube.
--- Further development blogging will happen in the 1.08 release notes.
posted
Fixed some glitches in the TRAX editor, which prevented you from modifying the duration/stress of a note.
But I still need to tweak it so you see the change in real time (instead of only after you lift your finger)
But you can definitely edit groove tracks now, so I no longer have an excuse to have any bad notes.
my 'tap to preview the current selection of notes' does a sort of 'play them all at once since you probably wanted to hear the chord' but I think it should include timing as well, so you can test some small melodic phrase by itself.
and I also still need copy and paste.
---
Oh, I tweaked the metronome
* quieter in general. It was pretty loud * emphasizes first beat of measure * first beat happens with metro needle 'on the left'
That last bit was reversed, but I had to add some 'beat visualization' stuff to prove it. Feels considerably better now and easier for me to really play along. Which I still do poorly.
Also, I use a single pre-computed table containing one period of a sine wave in full double precision glory. When I need a sample that represents a certain 'phase angle', I can only pick one of the pre-computed samples, so I end up with small errors that you don't notice too much when individual notes are playing, but become really obnoxios in the context of any sort of glissando. Like sliding your fingernail lighly along a nylon guitar string.
Anyway, for now I just increased the number of precomputed samples, but I am advised to go full broke and interpolate values (bi-cubically, I believe). I had done that origially (and I started with a very coarse sine wave table) but it felt like it still sounded pretty aliased, and increasing the samples worked.
And no, I really compute the full sine wave, even though I could use just the first 90 degrees with less logic than the interpolation is going to cost me (and thus get 4x the effective resolution with the same table size). But I tell myself this way I *COULD* have an arbitrarily complicated waveform. I just CHOOSE to only ever use a sine wave...
But I really should try some other wave forms. I hear just 'cracking' the sine wave is pretty dramatic (just sort of shifint a portion of it in place, leading to a pair of discontinuities 180 degrees apart.) But I'll never know, if I don't give you a way to 'paint your sine wave'
And the LFOs should really offer triangle/sawtooth/pulse
---
oh yeah, I remember. I worked a lot on this last Saturday. I added a 'mouth' object, which has (up to) five 'formant' objects
Each Formant is an oscillator that can play a note that shifts (smoothly, not quantized like all my other stuff) in pitch amd amplitude.
it;s basically another way to play back vocoder spectral recordings, with smooth transitions. Kind of like anti-aliasing in sound.
I think kthis is the first class I have designed in a long time that sort of makes sense as an independent object and isn't completely intimate with the details of a zillioin other objects it should know nothing about.
In this case, that just means I can feed it from either a spectral recording directly, or from a compressed spectral recording (the format I will use, maybe, to send speech between players)
unintelligible speech, but speechy soumdomg a;; yjr sa,e
Those would have been my last words had I actually just choked on this bit of pork chop at the same time as sneezing during an allergy attack after inhaling a bit of carrot. Let's try to end on something more upbeat.
Peace on Earth!
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
And, with that, I added the interpolation back, and I don't know what I thought I was fdoing before, but it wasn't interpolation.
Anyway, sounds great again, even with a small-ish sine table (I still have 1024 samples, but it is almost tolerable at 256 now, but still much nicer at 1024)
We'll see later if it created a performance issue.
Anyway, I am testing the mouth as a player first, with manually created test values. Then I need to generate symbol streams from the vocoder, and eventually from the 'persistent VQ'
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
Mouth itself works pretty well, but vocoder is still not playing along with nice recordings.
I came up with a not-overly complicated algorithm for assigning spectral peaks to formant oscillators, trying to maintain continuity such that the same utterance component is used to drive the same formant oscillator over its lifetime.
But first I just went with 'let the first five peaks be assigned to the five formants, in order, every frame, with no worries of continuity at all'
it had the expected defects, but actually works pretty well for test sounds that don't really have that much going on (I'm using another copy of synSpace as an FM signal generator, driven by an envelope that I then try to recover on the listening device, and then play back through the 'mouth')
Oh hey, New Kindle Fire's coming out (which is why they are flushing the old inventory with these nice discounts). Since the new units come in new colors, I am doomed to acquire one or two more.
AAnd to be honest, the cameras on these tablets are nothing to brag about, at least not the way I use them (shaking in the dark)
And it's not like yellow is my favorite color for tech devices... but it's a chance to come up with an appropriate synSpace pilot name. Canary... Banana... CanCan... Lemon.. Hmm.. I think I like the fruit theme. Maybe Lemonovski or some such.
To battle with my tangerine colored kindle, "Tangry"
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
Not sure if I mentioned this, but the script can now provide a working "Rank" for scripted bots with Thumbs (so instead of using the normal 'Fodder' stuff, you can say anything you like.)
I used it on the DOTC map to use "Tower" and "Minion" as ranks, and "Dread Pirate" in the tutorial. Just for fun.
I have to keep reminding myself that while my music synthesizer is largely pre-wired (which modules control which), but that I could totally a llow a scrpt to set up a different configuration (and even have additional assets). That moves the hard part (UI design) to the Starmap author :-), but a scripted music helper just sounds really useful somehow.
I need to beef up the api to make that less vaporware though. But if basically the script built a sort of text descrption of a synth (this many oscillators, configured like this, connected to these other modules, etc) and then pass that down to the game engine, which would manufacture a set of math that does exactly that
or something like that, though I guess I need actual patch FEATURES (like num oscillators) to be standard enough that it doesn't compromise your ability to save and restore PATCH assets.
Things I will probably do
will eventually increase number of contour generators (from 6 to 8 probably)
Will probably add a second filter (which perhaps would be invisible without scripting)
Should trick out the filter with controls for both cutOff and Q/Resonance (currently Q cannot be driven by an envelope, just cutoff and I am not sure I am doing that effectively for each class of filter, rither)
Need to give the oscillators a choice of signals (variable pulse, sine, triangle, sawtooth) and maybe 'draw your own' But if not that I am thinking you might still have a sort of SHELL editor interface that lets you move the center, and peaks of the waveform. So you select 'sin' and then drag the center to the left, and you get a sine wave that is skinny on one side and fat on the other. Plus distort/attenuate loudness in regions.. and that cracking/mirroring trick. It would be the same control (dragging a center handle) you would use to set the pulse width.
Plus, hard-wiring the oscillators to pure harmonics is not very flexible. I need a way to override individual oscillators and make them any offset you like from the fundamental. Though anything I add there has to be backwards compatible to patches that don't offer the feature.
For some reason I seem to have decided to make a pretty big change to my synthesizer. As you will recall, today there are nine harmonic oscillators which are forced to the exact harmonics of the key(s) you are pressing. Additive synthesis.
And by using several envelope/contour generators, you can modulate the contribution of those nine oscillators over the lifetime of the note.
Then THAT signal is fed into my single filter, which can have a dynamic cutoff freq, but has a static Q value
And my harmonic oscillators are all pure sine waves.
ANYWAY, that's great for a bunch of musical sounds, but not great for percussion and interesting 'dirty/grungy' sounds.
So, here are the changes coming:
1.) add a dynamic Q (Contour Generator, drives LFO which modulates Q in real time during note)
2.) lose Harmonic oscillator 9 (I don't want to increase the number of buttons on that row)
3.) Re-order them on screen to indicate wiring
FM -> OSC1 -> .. OSC8 -> Fc / Q -> AM -> reverb
4.) add controls to the oscillators (I think you tap them to toggle a detail window for a single oscillator)
The detail window lets you:
* toggle muting the oscillator * set its frequency 'offset' from keyboard (so any oscillator can be any harmonic) * select a wave form (sine, square, tri, saw) * configure a DISTORTION (+/- 1)
All the oscillators are driven from a common FM module (same as before) for their fundamental freq.
The distortion is kinda cool. Imagine a square wave. Now drag that 'falling edge' left and right to make a sort of 'pulse' instead of a symmetric square wave. Imagine a value that -1 means 'dragged all the way to the left' and +1 means 'dragged all the way to the right.
code:
+----------+ + | | | | | | + +----------+
+-----+ + | | | | | | + +---------------+
^ distortion = -0.5
Now imagine that that same slider works for all the waveforms. So a sinewave can have a skinny left side, and a fat right side, which is still smooth like a sinewave, but now has a whole bunch of interesting 'smooth' harmonics. (as opposed to the squre with a buunch of 'rough' harmonics)
You then pass that through filters to get what you want. And timevarying the Q is what gives you a familiar 'splat' it is impossible to live without. I hope someday to have more filters... eventually maybe just an array with 8 CGs, 8 Oscillators, 8 LFOs, 8 Filters, etc, and some sort of 'virtual cabling system' (cable 12 connects module N, output to module M input 2 and module X input 4..) and then just process the arrays in order with the 'cables' holding the most recent output they have seen, starting with 0 or something. Someday. And probably that's what would be script driven. But not today.
Anyway, each oscillator will have its own distortion setting, which lets you set the sound to anything you like, but would not change over time during a note.
But in addition to that, some oscillators (not all) will have dynamic distortion, where a new LFO/CG pair allow the distortion to vary over the life of the note, and THAT turns out to be the source of most of the grungier sounds
I Think I will do it like the EVEN oscillators will have a checkbox that turns them into a distortion LFO for the ODD osccilator next to them, so the ODD oscillator have more power, but you choose how many power oscillators you want (up to 4) vs simpler (up to 8) I could probably justify changing that to 3/6 and saving 2 slots for a second filter.
5.) Will probably increase the number of contour generators. Though probably not all the way to 8 yet. Definitely not enough to cover all possible combinations. But too many envelopes probably makes the sound too 'busy' anyway. Plus they add up computationally when a bunch of fancy notes play at the same time.
I'm trying to retain compatibility (other than the loss of harmonic 9) so old PATCHes still work as before.
the above is still vaporware, but I am drifting in this direction for the weekend. The vocoder is also still a bit torn up, and I am rethinking the basics of my spectral recorder as well.
The 'mouth' playback is so clean, that it makes me want to keep working on the 'ear' until it can provide cleaner data to the mouth. And now that I beefed up the visualizers, I see all sorts of suspicious spots where my math is probably failing. (part of that is that high frequencies also trigger a hit in the sub-harmonics, so I often see a low note that isn't really there)
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
hmm.. more meandering thought in place of action. Just trying to think of the component chain as a math equation and sort of simplify it down to a sequence of basic operations and make sure I can do that without excessive UI.
And I think one UI direction is for the existing sixe counter generators to become six (maybe 8) 'basic control units' which are still contour generators, but also include a range slider and a low frequency oscillator, with waveform selector, all in one package, though you can disable the LFO when you don't need it and it is just an envelope as it is today.
That lets me remove some stuff from the KNOBs page that was about to get even more crowded, and lets me move pretty much the same knobs to the individual CG pages, where it is not so cramped to begin with.
And maybe I could have sort of 'dueling tabs' so that I have a large empty rectangle that I can render a bunch of different things into, but only one at a time. Along the top are tabs for 6-8 CGs (as there are today) and tapping any of those tabs causes the center to display as expected.
But along the bottom of the flexi windo is a second row of tabs (in the form of the decibel sliders) and tapping any one of THOSE makes the center section display the details for an oscillator, (or a filter or anything else in that row of existing sliders)
vaporware: think of the row of sliders as being a 'eurorack' but with the concept that it also forces the wiring left to right. so each module drives the module to its right... not really thougyh.. really it's hardwired to one degree or another and I am just trying to convey the logical spirit of it.
In any case, a common render area with tabs above and below. Actually, add stuff to the side and it's the WarPath classic interface. In this case it feels like it could be a big win, and give me a logical way to provider easier control of little fiddly things..
time to draw some pictures! that's SORT of like getting something accomplished, rigth?
Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
ok, after drawing the same pictures over and over like I was drawing jet airplanes in grade school, I think I do NOT like adding an LFO to every CG. Just too busy.
But I DO like having a detail window for each of the attenuation/decibel sliders, showing all the knobs for OSC, LFO, NOISE, and FILTER style modules. And I think I see strong visual cues so you know which CG is driving which module features.
To handle filter Q, I think I have decided:
* still just the one decibel slider, whose value represents the 'Max Q' of the filter. The associated CG/LFO is still used to drive the center frequency (Fc), and normally Q would just stay where you set it so the 'width' of the filter would not change over the life of the note. One common trick is to just hook Q up to an LFO that is free running at 0.1 hertz, so you get a slow wah wah noise not synched to your actual playing, but 'cool'
* But has an optional second CG binding to provide the option of variable Q from 0 to MaxQ (I mean, when the CG envelope is at 0, Q is some minimum value, and when the CG envelope/LFO is 1, Q is MaxQ. linear in perceived frequency space :-)
This requires a means to set the second binding, since there is no explicit decibel control for it. I plan to render the envelope on the module's detail panel, and a long-press in that region will map the 'current CG' to be used (same as with the decibel controls)
Once I embraced the concept of maybe 2 CGs driving a single 'module' I decided to go ahead and let every oscillator have the option of declaring a second CG to drive its distortion value over the life of the note.
I will never have enough CGs to drive everything at once, but my math would laag out by then anyway, so hopefully 6-8 CGs will be enough complexity for any one PATCH.
So, when the dust settles, the only truly new bits will be:
* new 'distortion' value for oscillators, which can be set manually, or driven by a CG. Just slides the falling edge of the wave form to the left or right. Often called 'duty' when used with squarewaves.
* new 'freq offset' per oscillator, instead of hard wiring to the natural harmonics (but defaults to natural harmonics anyway for back compatibility). I think I will quantize this to half-steps (multiply freq by 12th root of 2, to raise it one half-step)
* new 'waveform select' per oscillator (and LFO), supports sine, square, triangle, and sawtooth (all with at least static distortion)(LFOs can't bind a CG to their distortion)
* optional second CG for Filter, to drive Q over life of note.
------
I'm thinking I should have set up an array of doubles somewhere, and defined an enum of all the 'important values' and then when doing the math, use one of the entries in this array as part of the actual computation (I mean it is up to date). Then I can have both a virtual cabling system (all module outputs are by definition 'numbered important values' so any input can just declare the index of the output it would like to 'be connected to'.
Also, you could have a nice selector so your 'oscilloscope' could connect to any of these for debugging
Once you have that, things like LFOs are just a resource to be used where needed, instead of being pre-declared in some modules, even if not used, and not available at all in other modules, even though you have plenty of unused ones elsewhere.
It's a lot like the way 3D graphics cards worked, with a fixed graphic pipeline, that has now been replaced by shaders, so the stock pipeline has now been pulled from the hardware. Eventually I will try to keep that promise of letting the script configure any synth you like, out of my base components.
* and maybe I should get rid of the percussion checkbox and just ship with a standard percussion PATCH which there is no reason to clone since you can't actually change anything and it's confusing to have multiple percussion patches around that sound identical.
Now, if I can get to the point where the wave files get played through the rest of the system (filters and reverb, for example), I can revisit this decision, since then you really could make new, different-sounding, drumkits. For this, I have to convince android to give me the raw samples up front, and it really wants to pre-cache them in some optimized location I can't directly read from (but that it can play with low overhead, so no complaint from me). But if I could just get raw 16 bit mono samples from the OGG, I could process them with all my other 16 bit samples and they would go through the filters, etc. Definitely more powerful, but possibly worse-sounding than the original clean recording.
But even so, all I am really saying is I would remove the percussion checkbox from the UI. If you cloned a patch from the stock percussion, it would work (and still be percussion). You just can't turn percussion on and off other than by cloning something of the same flavor.
* I should probably add some meta data like 'this is percussion', 'this is a bass' ' this is good for rhythm, 'this is lead' etc. So when re-casting, or auto-casting, it can pick something appropriate for the role.
* Perhaps I could be flexible about my CGs if I didn't predeclare them, but could add them as needed. So maybe 4 are predeclared, but I have namespace carved out for 16. As you add more, the tabs either get smaller, or at some point scroll.
But USUALLY you would only need a couple, so the tabs wouldn't have to be all scrunched up in most cases. Probably the Chrome Tab model, with a /+\ tab at the end.
It feels more than 50% likely that I will spend some time breaking the synth, with only a 60% chance I will be happy with the end result. I really need to compile some test cases to make sure I like the sound of time-varying distortion.
* I should rethink NOISE while I am at it. I tend to have a sort of geiger counter click at the low freqs, instead of a threatening rumble. I'm not sure I coimpletely understand how to make a rumble without is sounding like a hum. Right now I have what is in theory a very fast random stream. I then sample that stream at the frequency whose noise I want to produce, and hold the output at that value (a random value) until it is time for a new one (one period later). Oh you know what, the freq offset feature will help here. I can set a noise oscillator to fire octaves above the piano note so that it has many more random values per second, to fill in the 'clicks' and then put that through a low pass filter to get rid of everything but rumble, and then probably amplify that. Would I ever need two different NOISE sources in a single note? Do I have to add a NOISE checkbox, or can I just force it via the decibel button, so only one oscillator has that. Yeah, I am going to keep it a separate module for now, since maybe it will need funky config choices in the future anyway. not an oscillator, but a peer to oscillators in the big mixer
Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
So, I worked on the synthesize over the weekend and added the new 'module detail screens'
So that row of 'attenuators' can now be thought of as a row of 'synth modules' and tapping any one of them opens its detail panel with controls specific to that module.
I implemented the new Oscillator module with
* choice of waveform * new distortion feature * choice of freq offset from keyboard (instead of hardwired to pure harmonic)
The new Filter module now is driven by two separate LFOs. One for the center freq (Fc) and one for the Q/resonance. I've added all the controls, but have not yet replumbed the actual feature to use the new data.
I was able to make a 'better bass' by
1.) muting all but oscillator 1 2.) setting its freq offset to -12 halfsteps (drops it an octave) 3.) selecting square wave for lots of harmonic content 4.) set the center distortion to give the sound 'character' 5.) set the FILTER to LOWPASS mode and use an envelope that cuts the high frequencies right after note start (to get that stringy boing sound)
Then when I have the time varying DX set up, use another LFO to have the DX wobble over time, and maybe use the second FILTER LFO to maybe widen the band (lower the Q) at the end of the note, for a sort of 'splat'
What I find is interesting is that adding some harmonics to a very low note, allows you to hear the low note itself better. Just sounds 'deeper and more bassy' when there are also some harmonics around.
And, of course, you have to use earbuds to hear the bass, since Kindle speakers are pretty weak in the low end :-)
Definitely a step forward, though I have a lot of busy work to do to implement all the detail panels (13 total, but many get to use the same form, since, for example, I still have NINE oscillators, which is overkill in the subtractive synth
Oh, I set up a fairly harmonically rich sound, and bound it to 4 oscillators, and then tuned them to be a 'chord' (as opposed to 'harmonics') and then just wandered up and down the keyboard, and that sounded pretty fantastic
I think I set a standard for panels needing a 'second attenuator/second CG/second LFO' but that's still one of the larger UI changes, and one I am not completely happy with yet, but I think it's inevitable so I need to just DO it.
Also, doing the development on a 16:9 screen and likely will have to worry about 4:3 screens. Some of these buttons will be dangerously small on a 4" phone.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
Oh, and I think I will make the OSCILLOSCOPE follow the module. I.e. when you select a particualr module (to see its detail screen), you also set the oscilloscope to display the 'output of just that module' (in whatever way makes most sense for the module). Sometimes the LFOs add a lot of uncertainty and you really want to be able to see the data, especially during development when you're trying to prove the code (and empirically determine various constants)
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
OK, I think I got Filter working again mostly. It suffered a bit in the upgrade. Now both the Fc and Q are driven by their own CG/LFO/ATT triad, with explicit 'center' controls to set the baseline offset from 'the note' and the basic Q.
The OSCILLATOR module is also pretty much complete, but I need to whip up something for AM, FM and NOISE which initially will just be to expose their CG/LFO/ATT triads in more detail.
I changed the MUTE button (in top right of each module detail panel) to be a replica of the main attenuator for the module, plus you tap it to toggle mute, so it works like the old attenuators used to in that regard. The second channel, if needed by that module, is located just below that and is another standard attenuator
All very regular and pretty clean, once you know what to touch. Probably another overwhelming sea of pixels at first glance though.
Oh, right, I still have to wire the LFO for the DX control on each oscillator. I might just drive that from an envelope and skip the LFO. Not sure what I need/what will fit.
---
I rewired the oscilloscope as promised. So now tapping on any attenuator selects something relevant to display. I will probably have to add some other taps, like maybe the wave form selectors to select some other signals
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
And I have just now re-ordered the attenuators, so they flow from left to right in an order that matches my 'pipeline'
FM => Oscillators -> Noise -> Filter -> AM
with reverb being built into the player, as it were, and not into the patch. So it still gets set on the KNOBs panel and has no attenuator of its own.
In theory, I could let you drag them around between slots, if there were some game value there. Maybe that's how the scripted UI would work. Part done in script, and part done in flexibility in the standard UI. But not a priority at this time.
posted
after rejoicing in some Yamaha DX7 videos on youtube, I went ahead and added 'algorithms' to the oscillators.
Nothing fancy. My nine oscillators in a row can be thought of as up to nine stages of FM synthesis where the output of each oscillator drives the FM *frequency modulation) of the next.
Currently I have jus one control (per oscillator) that you tap to turn "FM CASCADE" on or off. When it is OFF, the oscillator sends its output to he mixer, as normal. But otherwise It tries to modulate the next osillator to the right. Eventually you need an oscillator to NOT cascade, or you won't get any audible output.
While FM synthesis is generally pure sinewave, these oscillators have all their normal bells and whistles, so feel free to break the rules and explore.
But basically you're just trying to make a harmonically rich environment that follows the note. You can filter that later if you like.
A nine stage cascade is unlikely to be very stable, so maybe you'd like 3 3-oscillator cascades, or one 4 oscillator, and one 5 oscillator cascade. It's your choice.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
That was actually kinda scary-easy. I guess now my synthesizer can do additive, subtractive, and FM synthesis all at the same time. I was actually at Stanford when Chowning was inventing FM synthesis (never met him, but did spend time in the lab), so my synth is now state of the art for just before 1980. Sounds about right.
I seem to have broken the vocoder again, so I guess fixing that is a priority. Plus, there is still something I can do that causes android to stop playing my samples. I can't get it to fail in the debugger so I haven't gotten to the root of it yet. But I suspect they have something to protect the user from apps that go crazy and send hyper loud high frequency noise (might even be bad for ears/speakers), since it often happens after my filter screeches.
Sometimes, it recovers after a few seconds, but often it just refuses to play any more samples until the app is closed and reopened. MAYBE I should have my AGC sort of look for that profile and do something proactive to avoid sendig it to the speaers.
but if I can find the true cause, maybe I can proactively recover when it happens. Then do both.
--- time goes by
Yeah, it seems the freq that freaks them out. I added something to aggressively shut down the AGC if I see more than N zero crossings in a single frame. That cured the 'shut down after filter screech (and also mutes the screech pretty effectively)
And I found a spot that could divide by zero inside of the high priority audio thread. Once that happens, the thread is effectively dead and that's why it stopped processing sound. That;s probably been a thorn in there for a long time. It surprises me that the debugger doesn't somehow draw my attention to it more automatically, but I guess sometimes you're just expected to know what you're doing.
Now, all this fancy stuff I just added means the CPU has more work to do when playing many notes at the same time. And if it doesn't keep up, you get a nasty scratchy sound. Luckily there's a profiler baked into Eclipse, so it's not impossible to work out which bits of your code would pay the biggest return if you can speed them up.
I use more than a few Math.pow(10.0, (N/12)) calls to get multiplicative factors for moving a desired number of half steps in one direction or another. Luckily most of them can be cached, but there are some baked into the 'per sample' math that I just can't do without.
--
For the alpgorithm stuff, a 'modulator' is one of my oscillators (sin/tri/etc) running at a halfStep offset from the keyboard note, passed through a dB attenuator, then used to modulate the frequency of a second, carrier, oscillator that would be running exactly the note frequency, in the absence of any modulation. And as the modulation signal goes up and down, so does the frequency of the second oscillator (vibrato like)
But in this case, the modulator is running something like 2x the freq of the carrier (+12 halfsteps for example)
ignoring the freq of the modulator, the carrier will be at exactly the note freq when the modulator output is 0. As the modulator output rises to +1, the freq will rise and crest at some distance from the note freq. I chose 'one octave' so when the modulator peaks at +1 you are up one octave from note freq. When it slides to -1, you are 1 octave BELOW the note freq.
But I could have chose 'two octaves' or 'twenty' and just used smaller attenuation values to get the same 'one octave' that I needed.
So it FEELs like I should have +1 mean more than one octave, but I have not found any web resources that say for sure what 'is the correct thing to do'
In fact, I first implemented it as 4 octaves, but I did it so comically wrong, I had to drop back to one octave just to regain sanity.
posted
tonight I worked on the vocoder and made it more aggressive in sensing the end of a note, so now it is much better at hearing several short notes in a row of the same pitch.
I also, I think, improved the start of the note, in the case of a strong start (I skip the hysteresis if I get a strong start)
It still has weak bass 'hearing' and seems to prefer to declare it heard the second harmonic (octave up, I mean) instead of the 'real note' but I hooked up something that tries to detect when something is just a harmonic of another note, and not an intentional extra note. That actually worked pretty well.
Still not as good as I'd like, though. Still records 'too many notes'
But improved, I think. This release also has some improvements to the Track Editor (so you can fix bad notes, if you have the patience to do so)
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
OK, well, in an effort to unify some stuff, plus add a new light to look at the world through, I am adding another synth module, but it is really just the existing vocoder, but embedded in the synth sort of like the mic samples were coming from 'oscillator 9' and then entered the synth pipeline, including passing through the new filter.
And maybe that's fun. sort of an auto tuner experience maybe.
but I propose adding another filter, but one that is simply tuned to exactly match one of the vocoder filters (the vocoder effectively manages 256 filters to do its binary search for acoustic energy in the midi note frequencies.) You would then provide an index number and the 'osc9' would output the microphone, as heard through that particular filter (Fc and Q used at that stage of the vocoder).
And maybe that's interesting/informative. I am hoping it sheds some light on the poor bass detection. Though that might just mean that bass always requires 'more samples' to be properly detected. I wonder if humans can accept 'delayed base notes' or if that delay is seen as a primary rhythm violation.
If nothing else it should be something like a harmonica/kazoo in musical capability.
---
And I mean that as in 'samples from a time period longer than one wavelength of the freq you are trying to detect. That you don't get the full benefit of noise cancelling itself out until you have a substantial number of periods of data. And that for a lower freq sgnal, the last few samples into the filter only represent a small portion of 'the arc' of the sinewave and no real estimate of 'time between zero crossings' So you end up with a really broad peak, even with high Q.
If you had the same number of samples, but only used every tenth one, your recognition would be delayed, but more accurate. I think. Maybe the musically responsive thing is to react the energy immediately, with a best guess frequency, that you correct as soon as you are able to resolve later. I suspect, for low freq, that people feel the beat, and tolerate a temporary pitch error.
But I am secretly hoping to find the magical filter indices that correlate strongly to individual types of percussion :-) Right now the vocoder pretty much ignores the beat and focuses on the notes.
ANYWAY, I didn't really mean to post about this yet, I just needed to move these notes to the other pooter: Notes to myself:
synthesizer() if osc9 get next sample from filter[ filterIndex ] output Q modulate as needed (resample data sent to filter?) (can I FM synth this stream? I guess if it is symmetrical, and I store enough samples to cover the spread)
onMicDataIn() if osc9 engaged update filter to match current Fc/Q only when it changes (probably no LFO at first send mic stream through filter (modulate rate? smooth filter changes? jump-changes?) filter out goes to circular queue... one buffer? in case of underrun -> silence overrun -> discard.... oldest
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
OK, did that. Oscillator 9 is still a normal oscillator, but the WAVETYPE selector selects Vocoder Index 0 (raw microphone) when you choose the sixth setting (flat line)
basically this just means you can
* wear earbuds * enable osc9 and set it to vocoder mode * When you press a key, the samples will come from the microphone instead of a normal oscillation. * after that they go through the normal filter * the filter normally uses the current key as the center freq
So the effect is that you hold down a key, then talk, and in the earbuds you hear your voice 'through the filter'
I can't say it has shown any light yet, but I think using a second copy of the game to generate a complex test pattern just might. Also, I think I need a way to directly record audio from android, not just listeing through the speaker
But what I HAVE heard is a lot of stuff that isn't the music, getting through the filter, leading to my declaring a note, when it was just noise.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
Oh, the new kindles came. Seem to be high performance, and comfortable and good battery life, but the speakers are not too great, possibly worst yet.
earbuds are full fidelity, of course, and microphone works well.
camera is maybe lowest rez I have seen in a long time, but I haven't really used it yet.
YELLOW color is not too obnoxious (I think I can now officiall play 8 player synSpace with tablets in colors matching the player colors).
Well, no green yet, I guess.
The new red is nice, and the new blue (darker, I think)
---
Here's a thing, though. I changed the game's primary icon just before/after first release. If I completely uninstall the game and remove its data and purge my cache, and reinstall it (on a Kindle), the proper icon will appear in the 'list of all apps', but the 'recent items' page, which I definitely cleared after uninstalling, shows the old icon.
And I am pretty sure the artwork is not present in the game executable itself.
Which makes me think the 'recent items' images come from another source. Something on the device I failed to clear, or maybe it always reaches out to rhe kindle app store to get those images, where the old icon can still be found.)
I was leaning to the former, but now I lean to the latter. Maybe that's all just HTML5 anyway. Even so, you would need a local cache for times of no wifi. So a cache I can never find is still probably the most likely explanation (or that I even still have it baked into my APK)/ This only happens on Kindle, with the same APK.
Of course what *really* bothers me is that I changed the icon, which I had already grown to accept as 'the' icon, and I am always loathe to revisit that sort of decision. And this one is pretty busy and dim, but I like the flow/rainbow/multiplayerness of it.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
last night I learned about "goertzl" filters, which are basically optimized to detect specific frequencies in a noisy environment.
Anyway, I thought I might try them in the vocoder. The idea being to use the bandpass filters to do the binary search to find likely locations of notes, then use the goertzl on the individual note frequencies in that range.
So that was a fairly easy change (and easy to back out), but measuring the effectiveness of the goertzls has been less satisfying, so I am back to running various signal generators with individual sine waves, turned up megaloud so the tablet can hear them. And, of course, I can generally only do this in the middle of the night, and right now I also have to run multiple fans because we're having a heat wave.
So, I backed it out for now, because I also made another change. A sort of 'equalization' that boosts the lower frequencies before sending them into the bandpass filter. Visually that made a big change in my perception of bass notes, but I'm not sure that actually translates to 'better vocoding' yet. There are definitely some mysteries. For example, I have a lot of incoming noise from the microphone. When there is a strong signal, that noise gets pushed down, but it is always there. And if I play a high sine, it looks like it gets through with just a little noise on it, but if I play a low sine, the noise just dominates, and there is an asymmetry where the top half of the sine (as seen in the micorphone samples) is clean, but the bottom half (the negative bit) is very noisy. I think that's a clue. Possibly that I live in a horrible acoustic environment of high frequency noise. I thought that was just my imagination.
But seriously, I might have a code path that goes weird for negative values, or maybe I have an undetected DC offset in there somewhere. Anyway, I will keep trying to make better flashlights until I can see it.
I was burned once with code llike this
int numHalfSteps = 7; // a 'fifth' double scale = pow(2, (numHalfSteps/12));
which should give me a scale factor I could multiply by any freq, to get a freq exctly that many half steps higher, no matter which octave you are in.
Only it didn't. It did something really odd, until I finally realized it was doing this part
(numHalfSteps/12)
as integers (even though the pow() was expecting a double, so auto-promoting for me, but AFTER the division.
So it was important there that I use (numHalfSteps/12.0) to make sure things got promoted to higher precision before the pow.
ANYWAY, I only mention that because it smells like I am doing something similarly awful in the vocoder somewhere.
But the filters themselves seem to be pretty rock solid, and I think I have proven I am setting all the frequencies to proper values. I think I am just running into a really high noise figure as I move to the lower end of the spectrum, and that is causing all sorts of errors.
And that noise appears in my raw samples from the mic, or so I believe.
---
You know, it suddenly hits him. I do this thing, in a past effort to boost the low end, where I duplicate samples so I can send 'more data' when scanning for a lower freq. If that code is insane and injecting noncontinuous crap... I should definitely turn that off, at least when I have my 'new' equalization engaged.
gee, that would be pretty great. Esoecially since the new equalization looks like maybe it actually works.
posted
When profiling the code (modern tools all have profiling built in. I just assume that data is usually beyond my access), anyway, it often points to all my 'drawLines()' calls in my Music Oscilloscope (Where I want a pretty faithful rendition, so I can't just lmit it to 'no more than N lines'
But in one of those rare moments of serendipity, I found a small change to my 'algorithm' that both makes the display prettier, it is also more accurate and considerably less processing overhead, without being some sort of 'smartly conveying more accurately, just at the locations of most change'. But a step in that direction. My flaw before was that at most zoom levels I was oversampling the source, so actually ADDING unnecessary lines, that also messed up the proper shape.
Win win
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
I added a limit (10) for the number of notes than can be played simultaneously. You can only start 5 at once since I only sense five touches, but if they have sustain and long envelopes, you can start new notes while the old are still playing and if you mash the keyboard you can launch enough notes to sort of crash the computation budget.
A fixed limit is kinda braindead since I can play lots of sine wave notes, but fewer complicated filtery notes. So I should probably categorize them by processing expense (as well as boosting performance where I can).
but for now, this avoids those embarassing stalls when I mash the keyboard, and I haven't noticed it having any effect on 'real' music yet.
I found some nice a capella recordings of Amy Lee that are fun to analyse with vocoder.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
I *almost* fell for a phishing attack today in a very convincing email 'from a friend'
In fact I MEANT to click the link but sort of missed, and then realized what I had almost done, and confirmed my friend had sent no such thing.
So, I guess all these years of Russian Spam have just been an attempt to diminish the value of the Internet, perceived as a western success, and thus in need of being tarnished :-)
Since the only game in town, east or west, is "your team sucks, and I will cut off EVERYONE's nose before I will accept any other determination"
Imagine if we had a government of intelligent people who were applying Musk-Like principles to solving Healthcare, instead of just trying to pee on the other guy for decades at a time.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
so, I got this very inexpensive chinese 10.1" android tablet. A "YunTab" which I think is trying to look a lot like an iPad.
It's built for an asian marketplace, I think. It has a little plastic cover, beneath which you can insert all sorts of things, memory, SIM cards (plural, I think), etc). It's android 5.x lollipop and has the Google Play Store, but I was frankly a little afraid of some of the stuff that was preinstalled (well, like the greeting web page it wanted to open), so I sort of reset a bunch of stuff before actually running it.
Getting it into developer mode was a little unusual, but pretty much the same as always. It took SS:DR just fine, but... the frame rate is kinda horrible. The screen is not particularly high resolution, so it doesn't have that excuse. It's only got 1GB of RAM, which looks over half in use at boot up. I tried dropping my sampling freq to 8K and even simple sounds couldn't keep up without CPU stalls. The touch is very erratic with frequent dead spots. Plus it seems to really push this protective plastic cover, like they know the front glass will scratch easily or something.
But it was very inexpensive, basically free since Amazon gave us a gift certificate after the snafu with the Plumber. It also has pretty horrible speakers, which is par for the course. It's close to pure android though (maybe it is pure). Not sure about the browser... not Chrome... make a note.. get Chrome... it DOES have GPS though, so in theory you can use your google drive directly from it. and 10" might be OK for a little scripting.
The charger looks particularly cheap and completely unbranded.
It's fine for streaming with earbuds, though, and a nice large screen that still qualifies as HD for me.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
Same Text I used on YouTube, but I wrote it here...
I got this cool cable that lets me drive the tablet's mic input from a 'line' source. This enabled me to play a known 'good' signal source into the tablet, by-passing the tablet microphone and speakers.
I spent the evening with the cable, playing various perfect signals into the vocoder and I think I was able to resolve a lot of small problems, leading to an overall quantum improvement.
One problem, for example, is my internal 'units' (I have no idea what they represent exactly, but my core unit was '10 million') and that led to a pretty frequent energy value that could overflow my integer values. And that resulted in higher freqs 'folding' onto lower ones, which had been a mystery. (happening because the higher freqs also generate the highest filter energies)
ANYWAY, Here's a link to the original "Peridot" as used in my synSpace: Drone Runners (available on Kindle Fire and in the Google Play Store, plug plug). It's a fantastic song, in spite of my leaving the camera running, as it were.
First it plays back the raw vocoder recording. This is a bit 'poppier' than usual because I completed removed the debouncing in an effort to find what was low pass filtering me unexpectedly. Then I added a different kind of debouncing to try to recover proper note start/stop boundaries. But I still need to match gain at the transitions, without blurring things. Anyway, more work to do, but this is a pretty faithful rendition of 'the important notes' so to speak. I think you could hand edit the result into something useful, if you applied yourself.
I then replay the spectral recording with the camera focussed on the 'formant view' (although this is in musical note spacing mode, not human formant mode). It's not particularly exciting here, but you can see some of the tone sweeps in the noise. Pretty much what you hear.
Then I delete the spectral recording (hence the desire to immortalize it when it isn't completely heinous) and play back the transcribed 'groove' using a random instrument, while the camera focuses on the 'piano roll' of the perceived notes. You can see see the trend to staccato-i-fication of llong notes, but its frequency precision has gotten a lot better.
I think.
I've made so many changes to the synth of late that I'm sure I've introduced instabilities. But I found a spot where I wasn't limiting the filter forner freq to the nyquest freq, and that was just asking for the filter to go crazy. I kind of did it on purpose since I thought it might be sort of cool, like 'overdriving', but really it's just a very unpleasant, and loud, sound. I think I have all the filter 'sweep' I ever had (and more, actually, since I increased the limits on the 'center' controls. (the half=step increment controls)
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
It's a bit of a lever arm and likely to break something. And it would be nice if it could also pick up the headset mic, when present. And I'd lik,e to be able to adjust the headphone feedback level
And it's not PERFECT, but it is satisfyingly minimalist. The goal is to be able to wear earbud and speak/sing into a nice microphone, connected to your mobile device, and you can hear (via earbuds) BOTH the normal output of the device, AND the live loopback of whatever the microphones hear.
So you have live lag-less echo of your voice, no mic feedback howl, and uninterrupted access to normal device functions without needing to switch cables frequently.
Add to that a 'personal silence mouth sock device' and you can finally entertain being a singer without anyone knowing. Silent Singer. With an enthusiastic group of background bot singers and musicians). I'm way too shy to sing, but I love resonating along with in-tune sounds.
I also tried one of these, and the jury is still out:
I guess it's got a lot more gain, so the vocoder records a lot of noise. Time I addressed AGC on the vocoder input. So that interferes with my assessment. Plus, this results in a ton of extra 'notes' in the groove, so it is 'bad', but really it is my inability to deal with a variety of signal levels that is 'bad'.
But it also might be a piece of junk. I don't know for sure yet :-) It SOUNDS perfect, if the mic signal really is fed back directly to the earpiece (requiring some active mixer circuitry) or if this is just a piece of plastic with some wires in it) I'm conflating the two products here, but that's my main concern -- live lagless feedback of the source material (mic), while recording. Otherwise, you get all stuck in the brain.
Often, the air around you let's you hear what you're saying, but you really want to be able to control the mix. Well, I want to.
Plus if the mouth sock works, I won't be able to hear what I am saying otherwise.
The could come with optional covers. I think BANE would make a nice one (if I remembered that right).
Darth Vader... friendly puppy. Lots of memes possible. Just has to not actually suffocate you.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
OK, time to get 1.0.7 released, so I can move development to my new pooter, now that it is all installed the way I like it.
Mainly it looks like I just need to smooth off the rough edges in the Trax Editor, make sure the Vocoder isn't too broken, get a little more performance out of the music engine, and add another StarMap.
---
Not in this release, but I think I could use the new ALGORITHM knob to direct each oscillator to more sinks. For example
to MIXER (straight to output, as is) to FILTER1 (straight to filter1, then to mixer) to FILTER2 (a second filter, replacing OSC 9) to FM cascade (modulate the guy to my right) to AM Cascade (modulate the guy to my right)
Again, a step short of a full cross point switch matrix, but I think it could cover the main things you would want to do.
---
For the track editor, I just want to make it a bit easier to preview sections of a song, over and over (tap replay, move a note, tap replay..) That's a little clunky at the moment.
For the new StarMap, it should be tractor beams. Something about tractor beams.
For copy/paste, I guess I have a clipboard that holds stuff you can paste into any track as an insertion (pushing other notes back to make room...) really? in a multitrack environment?
I think it's great to hold off on that until it's more clear in my head. I need to cover insertion and overwrite, silence addition and removal, and merging completed tracks into each other. Actually maybe track merge is what I should do first, and then see how I feel about cut/copy/paste after that.
Well, it's a goal! Gotta have a goal at the start of the weekend, right?
posted
Pretty much didn't do anything of that. Had day job issues on Saturday, then just felt lazy on Sunday.
But this evening I was playing with my magic cables so as to be able to vocode directly from PC sound, while hearing it live in real time.
That gave me a platform where I was able to fix my long-term average energy code, so in theory I can pick notes out of both loud and quiet sections of a song 'equally well' now, but it means I have to adjust a bunch of magic sliders again to get a stable situation.
It can track pretty fast notes now, but it still hears notes that never should have happened. With a tone generator (online!) I noticed that something odd happens just below 100 Hz. I think it's the low end of the microphone input on the tablet, and once you get there, the signal level just drops off, and my code has a sort of AGC effect that makes it record a bunch of static until the real signal returns.
my long-term average energy is supposed to let me track the real loudness of the sound, then changes in loudness that are roughly half the total, are significant for note start events. Once I 'hear' a note, hysteresis lets me continue to hear it as it gets very quiet.
So I guess I am actually pretty edge sensitive and should run it through a high-pass filter to find the strike moments, then back-adjust for measurement lag. Maybe I should try to turn it into inertia/momentum math to extract the time signature and beat stress. Sorry to just make up terms there. I mean most of my work has been in frequency space, detecting note color, and I really have not spent enough time on actual rgythm and beat stresses. or even 3/4 time.
Hmm, beat detector. part of the metronome, UI-wise. I get 50 sound packets a second, I believe, and I already compute the energy in each. So, I think i need an 'estimate beat' and then I compare incoming pulses against my local memory of pulses heard plus the current estimated beat delay. When the estimate matches the real beat, they will be in sync.
try to hear the actually main stressed beat, and estimate time/notes between them, to work out 3/4 or 4/4 and variants. Surely I have done this in the past? How could I not have done?
But actually, you probably want to listen to individual voices first, THEN listen for the rhythm... In my case, I think that just means using the wider bandwidth filter energies. Like, the midi notes are 128 in the 7th row, but the third row only has 8 'fat notes' in it (that span the full keyboard), which could probably be mapped to 'drum, bass, lead, and cymbals' which then could be individually tracked for rhythm.
output is an actual beats per minute, and a synchronized metronome. (it dynamically changes to match what it hears? Or maybe only for the first few seconds after silenc?)
changing the time signature has implications to the sequencer, but mainly the 16 step loops will become 12 step loops for 3/4 and other multiple of three time sigs). Really those are the only options, 4/4 and 3/4, probably. 6/8 would be the same as 3/4 at this level, just fewer '1-us-and-uh' stages.
In any case, I think I need a couple seconds of storage, of 50 pkts/second, so let's call it 256 of these little sound buffers. I just remember the most recent 256 buffer energies in a circular buffer, and can look for matching peaks different distances in the past. for a slow song at 20 bpm, that's 3 seconds per beat. That's 150 buffers per beat. If I want to be able to confirm at least 2 beats out, I need at least 300 buffers. So, lets make it 1K instead. Memory is cheap. I need 8 of these, so 8K. Probably float values at 4 bytes each. so 32KBytes. I think I can afford it.
code:
Each Audio Frame (50 times a second, 20ms) energyHistory[hist++] = energy(frame); hist %= 1024; // last 20 seconds autocorrelate(energy(frame), energyHistory[hist-estimatedPeriod] use correlation to adjust estimatedPeriod
given an estimatedPeriod (time between beats), try to find a repeating stress pattern
So, given one of those history buffers, just scan it for local peaks to find beat starts.. yeah, high pass filter it... take the derivative! look for strong rising edges (note starts) and falling edges (note stops)
Then using a known synced spot, and a valid estimated beat period, you can check repeatedly for new beats arriving on (and off) of schedule. Of the ones arriving on schedule, you track the absolute value of the first derivative at point of deflection and try to find the regularly occuring stressed copy, and count the notes between stresses.
Then, finally, use this tone-agnostic beat information to schedule the actual note hits we got from the tone info. If both rhythm and tone channels say go, then emit a note. (but snap to grid and emit in sync with sequencer, which is kept in sync with metronome, during recording from quality misoc source. When recording from YOU, it should not adapt to your erroneous beats any more than absolutely necessary.
I guess it could adapt to you, and then try to pull you back to proper time.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
So, I made a little progress on this, at least experimentally. New control "The Rhythm Rainbow" which is a moving stripe just below the vocoder filter energy display, that shows 'rising edge sound events' for each of 8 'bands' of the full spectrum.
It's kind of cool, but shows that a single pure sinewave signal, generates enough energy in all bands, so as to be almost useless. But this might mean that I just have bad Q values for the higher order filters, and perhaps I should use my setup to fully characterize those guys.
Visually, it is kinda cool though. Like an alien transcript of a conversation. I might leave it in just for eye candy
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
the vocoder output has always had a sort of motorboat noise in its output, at the frequency of the 'spectrums per second' rate (about 30 Hz)
It still does, of course, since averaging signals over such a long period (30 ms ish) leads to a lot of variety, especially during times of low signal level.
But it was doing it particularly a lot, because of some mis-managed math. Which I will now document in all its boredom.
A 'spectrum' in this context is an array of integers, one per midi note (128 potential notes)
The integers are the 'energy value' obtained by passing the samples (for this spectrum period) through a bandpass filter, centered on the midi note frequency.
And for a loud note, this can hit a couple hundred million (so watch out for overflow!) in my standard energy units.
So, for each timeslice I compute a spectrum and store it as an array of ints, plus some extras like 'the highest value present in this array of ints',
Then, in the analysis code, I usually work with relative values, so I divide each of the energy values by this maximum to get relative values in the range 0-1. I do my feature analysis, and rendering based on these relative values, since those are done on a per-frame basis.
But that means that a loud frame and a quiet frame display equally tall on my 'oscilloscpe', and it made the rough noise of a quiet period look peer to the smooth tones of a loud period, and that distracted me. So I added a minimum energy for the DISPLAY code. So the waveform always fills the vertical height of the control, until it drops below that limit, at which point it is visibly smaller down to silence. It's nice. I like it.
But somewhere along the line, I picked up a need to recover energy values from these relative weight values. Which means just multiplying them by their 'rowMax' values to recover the original energy. But, in a couple places, I used the 'adjusted' rowMax instead, and that led to the quiet frames showing up as (loudish) motorboat on top of the real signal.
So the fix was an immediate astounding improvement. But the following day, I still hear enough motorboating to be unpleasant, which I guess is my sensitivity level getting recalibrated and letting me know that my parental love for this component should not be expected to be shared by joe consumer, who would just prefer to have something that works. The philistine!
But I also only recently addressed another issue that also came from my looking at each frame as an individual. It ends up smoothing things out so as to be difficult to work out the beat info. Of course, the info is there (and conveniently located in that 'the highest value in this frame' number), but I was using the frame-centric stuff, and had no concept of the overall loudness of the song, with which to judge the importance of individual note contributions that lasted more than one frame.
This improves the dynamic nature of the recording, but runs the risk of losing quiet notes following a loud period. And in general dismissing quiet notes out of fear they are noise.
I should probably try more for "based on what I have heard, I think you're trying for the key of X and your rhythmic pattern is sort of Y. So if I experience a quiet sound, and it fits that pattern, I will choose to hear it. Otherwise, I will send it to some other channel (the unknown sound channel)".
But it's also possible I just overcorrected and a happy medium would match human experience enough. (After firing the canon, how soon until humans expect to hear a whisper?)
--
Come to think of it, when playing back a spectrum I actually try to move smoothly between energies, so as to motorboat as little as possible, but maybe that smoothing could benefit from some tuning in light of the removal of the error-based-boating.
Visually, it looks like the peak detector feature is working pretty well, assuming the filter energies are really as they are displayed. But my feature-to-keypress code still lacks discrimination. It gets all the correct major note events, but then adds 2x additional fantasy events, based on harmonics from the real events.
--- I think the scientific filter response analysis paid off with better selection of Q for the individual rows of my filter tree. I just need a parallel neural network that remembers sequences of filter combinations that coincided with specific key down/up events. (and it could generate its own training data by randomly playing notes out of its own synth)
so, how would that work...
testdata 'plays' and my algorithm has access to both the incoming microphone signal of it being played, as well as the midi note info that initiated those sounds.
each frame I compute my full filter energy matrix
that set of 256 values becomes the input to my first layer of neurons.
I have N layers, with 'some wiring' less than the infinite number of possible combinations between layers.
Initial weighting is random, but filters 128-255 are given high precedence by the 128 output neurons (individual key outputs. triggered in the presence of that frequency as a non-harmonic)
Since the training data is generated, it can be designed.. single notes first, rhythms, simple arpeggios, chords, chaos
anyway, so when an output neuron fires... or is the ON state, we check if the MIDI file agrees, and maybe check the delays involved.
If they don't match, push some feedback up through the network to make them match
---
A clear example
A Note is playing Its output neuron is ON The note has a third harmonic This is heard by another neuron that thinks a second note is playing so that second neuron needs a NEGATIVE weighted connection to the FIRST filter.
So each output neuron (representing an individual note) is POSITIVELY weighted to its matching filter, and NEGATIVELY weighted to the filters of all frequencies with loud harmonics matching the output neurons native freq.
and also POSITIVELY weighted to all other filters of the same 'note' (all octaves above) but maybe NEGATIVE for octaves from below
So lots of logical interconnection possibilities and natural wiring. (ooh, using it to generate training data includes using different synth instruments as well).
Given the quality feature data available, this has to be the most likely to succeed neural network project one might hope to run into.
So, to discourage error, I would:
* starting with the neuron that triggered in error * walk back through all the neurons that were providing positive input through a weight * lower that weight a teensy bit. Possibly less per layer..
And in the case that the output neuron failed to trigger in the presence of signal, then walk all the connections backwards until you get to the peer filter and then raise the weight fractioinally along all those paths.
I'm pencilling in 2 neuron layers of 128 each, the far right being the output which is either on or off, based on whether the note is present, as a note and not a harmonic.
I limit the total connections as follows
for each layer for each item of this layer (filter or neuronoutput) pick less than 128 of the dudes on the layer above
you definitely pick 'yourself' (filter1 -> Layer1Neuron1 -> Layer2Neuron1
you definitely pick 'your harmonic peers'
You definitely pick 'your octave peers'
Let's say.. 8 or maybe 16 connections from one neuron output to inputs of neurons on the layer above. And to be honest, I don't need all 128 frequencies, really probably only 80. so let's call that 10 connections each so 800 connections per layer. or 1600 connections for two layers
and is it worth it to make the connections rebindable. I guess it's how I would have to implement it anyway
code:
class Neuron int indexId float dendriticInputWeights[10] // no more than 10 int dendriticInputIndexId[10] // but any 10 you like init()
class NeuronLayer Neuron neurons[80] // 7 octaves or so void initializeLayer()
class Brain NeuronLayer layers[ 2 or 3] init()
// structure the code // compute the filter energies // compute each final layer output neuron value // by recursing through all its connections from the layer below // and then applying its current weight // and add together // leaving behind a list of the indexIds encountered // when done, set final output state
onFrameIn() computeFilterEnergiesForTHisFrame() for each final output note neuron IndexId val = calculateNeuronOutput( IndexId ) (emit note if val has rising edge)
-- this is reccursive calculateNeuronOutput( indexId ) tot = 0 for each input inputId = the id of neuron or filter data w = the weight I have assigned for this input e = the current level of the thing driving that input (neuron or filter data) as in e = calculateNeuronOutput( inputId ) tot += w * e
if( tot > trigger ) return 1 else return 0
And I guess you'd mark all the neurons 'dirty' at the start, and then clear them as you worked them out, so for a single frame, you would only reevaluate each neuron output once.
Also it means I don't need hardcoded layers really, and can have an arbitrarily crazy network (of small size). Just a flat array of neurons with 10 indexed weights each .
OK, and here is is. Each neuron also has a 'most recently considered' timestamp in it. Each time I do a top level call to
calculateNeuronOutput
for one of the output notes, it creates a new timestamp, which is sort of a generation id, monotonically increasing only when we compute a new top level note.
Then all those recursive calls, set a generatin value in each neuron they touch (they had a non-0 weighted connection to).
then when the answer is known, to be right or wrong, we can just scan the entire neuron list looking for neurons with this generation id in them, and punish/reward them as we see fit.
And we could do this in real time
OK, my goodness, a cool new project and it is only 3pm.. plenty of time to change the design another nine times.
for starting the training data, how about I just hit a note on my little piano, set a 'training now' bit, and then the normal listening code which was listening for things anyway, each time I reports something, it checks against that traiing now bit, and applies rewards/punishments for as long as the error exists.... Oh, I already have a bitmap of all the keys I feel are being held at the moment... perfect. Just have to not punish in the case of lag between good notes.
Still, point is, an imperfect test frame already exists.. How would I visulaize this. Because of the concrete nature, it seems something really logical could work. I guess just lines between the nodes, of thickness/color following weighting.
And what's a weight anyway... I'm thinking it's always an attenuation. If you need amplification, maybe you adjust the trigger level of the neuron output.
hmm, actually maybe you only increase attenuation when you get false triggers, and then lower trigger level in the 'no signal reported' case.
And then maybe periodically rebalance a neuron (adjusting all input weights, and the trigger level at the same time... to get the trigger level back to 1.0). You do that while you sleep.
how much damage could I do?
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
OK, so I added the world's smallest neural network, as an experiment. Basically it is driven by the spectrum filter energies and then tries to light up only the neurons representing actual notes in progress, but ignoring false positives from harmonics of lower notes.
So basically the first row starts with the filter energies, in order, and the last row drives the final output, the same order. in between is only one more row of neurons. It's wide (60+) but shallow (3). A correct answer is a path straight down the page, as it were.
Each neuron has exactly 8 inputs. Each input is assigned a 'neuronId' of the neuron it is listening to, and a 'weight', which is 1 if the neuron considers this input super important, and 0 if it thinks it is noise. Usually it is somewhere in between.
My current training sets all 8 inputs for every neuron, such that it promotes input from the guy right above it, and demotes from side notes (sort of insisting on a 'peak'), and also demotes from various half-steps down the scale, like 12 (one octave below it. If there is a note playing down there, we might just be a phantom note. The first row is allowed to err a bit on the side of generosity, to give the second row a bit more to work with.
But my live training algorithm tries to do this:
If I know he's playing C, but I don't hear C, then I recursively walk back through all the inputs to the 'C' output neuron, and for each input with a non-zero weight, I fetch the output of whatever it connects to, and then follow all THAT guys inputs, all the way back until I reach filter energies.
Then I multiply each energy by the weight assigned to the input in question, add all those products from the peer inputs of that neuron, then test that sum against the trigger level (1.0) of that neuron.
Um, I wandered there a bit, but I walk back to all the neurons that SHOULD have contributed energy (because I found the energy was there, when I walked back), and I amplify the ones I think let me down the most, by just a little bit. And sometimes a little random jiggle maybe.
To amplify the effect of a neuron, I lower its trigger level from 1.0 (so the same inputs find it easier to trigger it). To diminish the effect of an input, I lower its weight from 1.0.
And I believe I mentioned maybe a normalization/rebalancing operation would be desirable to get all the trigger levels back to 1 by making symmetric adjustments to all sliders.. done while you sleep, and the output could be displayed as 'a dream' :-)
But probably that won't be required.
if it looks like it actually WORKs, I might add a layer.
Technically, I don't have layers, I just have a single array of neurons, but my initial wiring between them honors a layer metaphor initially.
I can apply training data from an array, and I have a hand-written array to start things off (and honestly if I get that right, I probably would just stick with it and bake it in).
But you can also train it by holding keys on the piano keyboard. It will simultaneously play the synth, listen to the synth, decode what it hears, and compare it with the keyboard, and apply corrections to the network, all in real time with a cute little display :-).
I have to be a little tricky because of the delays in the loop, but other than that it's like the most perfect training scenario you could hope for.
Just to fantasize (haven't actually TRIED live test data yet :-), I could have it listening to Bach, and note it transcribed a rapid sequence of notes incorrectly, then pause Back and pound out the correct notes (not in correct order or anything, just keyboard mashing in all combinations), un-pause it and continue. And end up with a perfect rendition of Beat-less Bach.
But there's work and a SpaceX launch before that can happen!
---- update:
I am forcing myself to back-burner this until after 1.0.7, but am pleased with early semi-results. But somewhere along the way I decided the RGB values of indivual pixels are just data (as recently proven in material space :-), and wy not make them some number, say, six 4-bit numbers (instead of 3 8 bit numbers) and use those to weigt the input from 'the layer below'
And then the neural network itself is implements as just a stack of color images :-) Things you can look at :-) That always appeals to me. Stuff to look at while math is happening nearby.
In my case, each image would be 12 pixels wide, for each half-step of an octave, and then 6 or so pixels tall, such that the same note as me, in the octave below me, is located just 'south' of me in the image.
Then every pixel in the top image is treated the same.
Except on the edges, each pixel is surrounded by 8 neighbors, and has nine neighbors on the image just below it.
In this 'design' a pixel is a neuron, and is only influenced by six of its neighbors, all on the plane below the pixel's plane. Information can only flow upwards. It can only move sideways one step per layer.
Each weight, 4 bits, is only 16 values, (like -8 to +7)
Extremely limited in scope, but massively efficient to compute on a GPU. I claim. But, what is the output? Where is your state? What gives you the right to call these neurons?
You're right, I am a sham, a hollow con-man grasping at buzzwords! But I was thinking 'maybe the output is 'the sum of the weights' (or some algorithm, not additional state). So like there is data you need to have around, and data you can sort of create on the fly as you need it.
Like if I need to know what this guy's output is, I have to actually drill down into his (nearby) inputs and do the multiply-sum to get his current excitation and see if it meets the trigger level, then return either 0 or the trigger level. or some rule, that only requires knowing... well.. ulimtately it has to be based only on the RGB weight values themselves. So a neuron with a 'weight of RGB' also has an 'output of RGB averaged in some common way we all agree upon and can never vary, unless there is some sort of global or per layer effect).
ANYWAY, I'm sure it's too defective to actually work, but:
1) you could WATCH it? it's all images!
Anyway, as I started to crawl into that rabbit hole, I decided this was fun I could have later.
But note that you could use the pixels location XYZ and an algorithm to tell it exactly who its six significant neighbors were. So it could see from far away. And then maybe 3 8 bit weights would be enough for interesting things.
posted
you know, rather than this training being a universal 'decode any song', it might be better to think of it as 'learn to understand a specific instrument better'
(i.e. THIS instrument has particularly strong 3rd harmonics, so it benefits from different training... that sort of thing
and then maybe when it meets a new instrument/speaker one day, it can automatically drop into a little training if it has reason to believe it is getting garbled input
so the neural net ends up being a sort of adapter from one instrument sound to a 'standard' effect.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged
posted
So, getting back on the 'release 1.07' train, I have put the neural network into the background for a bit.
I finally managed to stay focussed on a couple tasks and was rewarded.
As I have mentioned a zillion times, my filter array has a fondness for the higher frequencies, and I have tried repeatedly to compensate such that my note peaks would all have about the same height and sharpness across the spectrum (when viewed on a logarithmic scale), instead of getting all fat and short below 200Hz.
As I also mentioned, I surmised that the fundamental difference was that N samples of a higher freq represents many complete periods of that freq, while N samples at a low freq, doesn't even span a single period. And that it was specifically when you dropped below 'one full period' that the results became chaotic (in addition to being 'blunt')
So, I changed that to
1) maintain a bit more history (samples going back a full second) in a circular buffer
2) use only as many samples as I need, into each filter, and compute the aggegrate energy that comes out. Use the newest samples I have.
3.) for lower notes, I have to send in loads of samples, (with some time dilation, so I want to send as few samples as possible. Using 16 full periods of the frequency seems to work well).
This did exactly what I expected/hoped-for and now all my peaks have the same sharpness (on my log scale chart), no matter where they are on the spectrum.. well, down to 50Hz or so.
yay!
In fact, I don't guarantee 16x at my lowest notes, and it would be better if I did, frequency-wise, but the note-smearing would be bad, so this is my trade-off for the moment. It's not a hack, it's a trade-off.
Anyway, this hugely improved my low-freq response, so even the butchered voice stuff is improved. Dare I say, it might be 'good enough' to meet my goal of 'sounding a lot like speech, but not in any danger of perceiving individual words.' Multiplayer-safe international emotion-speech
I think my biggest defect now is the 'trill' when I can't decide between two notes and rapidly cycle between them.
Anyway, it felt good to tear out a bunch of hacks and return to something approximating math.
Also, I got it to run 30fps again on my tablet (while spectral recording, I mean, which is a lot of computation).
So a definite step towards 1.07 finally and not another digression :-)
----
After I wrote that,
I changed the spectrums per second value from 32 to 24. In fact, it was never actually getting the full 32 since it couldn't run the game over 9 fps while displaying this computation (the render is expensive).
But now I can keep up with 24 fps while recording and displaying the data. It's a big win to have the spectrums occurring at a regular rate.
I think now that it was a mistake to focus entirely on the spectrum (as mentioned before) so the rhythm decoder is destined to be the final arbiter of note start/end times.
Something like 'do the freq stuff, to get estimates of notes that occured and their approximate start/end. Then apply the rhythm decoder to pick likely moments where it actually happened exactly, and adjust the note model, and add it to the recording at its adjusted location.
Notes can arrive in any order, get added to the event stream, and then get resorted by their start times (yay, not really threads!).
In theory, you could see this happening in real time on the track editor, though the way I handle the cursor there prevents it. I should change that.
In fact, smoothing some rough edges in the TRAX editor is one of the 1.0.7 priorities, so I should get on that.
* adding/removing space * merge track into track, with offset (leave new notes selected in merged track) (leave source track alone)
Or is track merge some sort of abasement of copy/paste?
Then a clipboard is the metaphor for temporary storage, and the action is broken into pieces, which might make the UI easier. confirmable/undoable copy/paste/delete
But note that full track merge is what I find myself really wanting to do with minimal fuss.
And start/end cursor markers, clearly on my ruler, with zoom options appropriate, and a better ruler button set for zoom/play/pause with a play from start to end for reviewing a specific section. And 'play selected notes' should play them in sequence or as chord, intelligently
And some sort of upper right [X] to close the track editor (really just collapse it down into the normal small view)
Plus real-time and after-the-fact time- and key- signature detection, and chord progression annotations, with suggestions.
a simple gesture for 'start new recording as a new track in current GROOV' on the vocoder display. Maybe long-press on the record? with popup confirmation that acts as the 'go' signal.
Somewhere around there it should auto-detect some stuff and adjust its own BPM to match and sync, and then the spectrums should be sampled at a proportional rate to the BPM. Ideally at least 3 spectrums per shortest legal note duration. Which at 60BPM, at 4:4 time, there are sixteen sixteenth notes in one second. So you would like 48 spectrums per second just to do that semi-reliably, especially since 120BPM is more likely.
So call it 100 Hz minimum, 10ms, for good rhythm beat location resolution... That's... 160 samples. And I can't use my deeper sample history here. I just need to simply track energy at that rate and look for edges coincident to freq info. I guess I would naturally see this channel first, and could then 'listen harder for' that which I expected to be appearing in frequency space soon.
The recorder should tag its notes with one of 3 or four standard instrument patches (which you can re-assign later with the BAND panel).
For example, really low notes get sent to the bass/rhythm instrument, stuff in the middle to the lead instrument, and broad spectrum high freqs are assumed to be percussion of some sort.
---
So, crazy thought time... Human speech is a bunch of individual notes that glide between frequencies (I mean the 'Formant' stuff).
In this way, they are much like bird chirps, just lower pitched.
Now birds.. as you know.. evolved from dinosaurs, right? So probably at some point in that process, dinosaurs did some form of chirping. Maybe not T-Rex, but a great great... great great grandchild of his maybe.
But given millions of years, and thinking how we think it generally played out, there is a time where mammals come on the scene, and I am guessing they are mostly food for the remaining dinosaur grandchildren, and if the dinos are chirping, the mammals are learning to listen for that.
And, my argument feels pretty specious now (since these early mammalians are also dino grandchildren in their way and both already have lungs and mouths and such.), so we'll tag this as creative writing..
So, some time after that, the surviving mammals try a few chirps of their own. Is chirp the only way to do it? Is it just a coincidence? Or do humans ultimately owe the gift of speech to the great grandparents of birds?
And at sunset, during the great bird internet, what are they actually downloading? Enter our hero or heroine, who has just written a game program that unintentionally can decode the language of birds, and they're....
----
actually, THAT would be cool. If I could just have it decode actual bird chirps. Like when it felt a real bird had just chirped nearby, like if it could discriminate Just That Well and No Better. Then, and only then, might it semi-randomly chirp back, in a way intended to make the player think way more is going on than actually is. Also might work as a bird repellent if you needed that.
ooh, an AI that listens to crows and establishes 'the most common words', finds structure we don't see there, and works out how to say 'this food is bad, but over there are some delightful crow foods' (a deceptive scare-crow, potentially targetable on your enemies!)
And then the same thing for humans. Little conversational units that talk you out of your bad actions before you commit them.
NOTE: I honor the noble crow and would do nothing to harm them. They apparently have long memories and can recognize individual humans, or at least think they do. Hear that Graywing?! That wasn't me! How much corn, Graywing, ... how much corn do you really *need*?
(dramatic fiction, I assure you, but I am circumspect around animals I think could choose to poop on my car if they had a mind to.)
I also saw a film of a crow scaring a small animal into traffic intentionally as to get an easy meal.
I wonder if that was real now. Is anything on the Internet to be believed? What happened to our 100% accurate Internet full of the unchallengeable wisdom of the ancients?
Oh right, that's me. I am the one true source of factual reality. At least it is my duty to be so, for my own protection, as I read my way through the Internet and its various propaganda. This is what we all need to do.
-----
OK, back on the 1.0.7 train tonight, and my big build for work is now done, and maybe this is the moment I finally get to check it all in and get into beta.
posted
OK, well, I was watching a youtube of a VHS recording from.. the 90s? with a guy reviewing the state of music synthesis up to that point. Very cool. Anyway, got me feeling inadequate about emulation and I had a small User Interface idea that allowed me to hook up the pieces with very little work.
The changes are:
On the vocoder, you can tap 'above the spectrum' to select one of three playback modes (anywhere in the left third of the area selects 'raw samples') so when you play back the spectral recording, you hear something approximating the samples used to make those spectrums.
You then use the spectrum range control to find a a piece of the recording you would like to use. It can be up to 2 seconds long. Tap PLAY until you have it lined up as you like it.
Once you have a range selected, switch back to the SYNTH page of the PATCH you are working on and notice the oscillator waveform selectors have a new graphic that looks just like the vocoder button
a LONG PRESS in the waveform selector will bind the current vocoder selection to that oscillator slot.
Thereafter you can set this oscillator to any of the normal waveforms (sine, square, etc), or to this stored waveform.
The synth remembers nine sample slots, keyed to oscillators 1-9, but shared by all patches on the device (and not shared between devices), so this is just for personal amusement at this point. Or unique live performances :-)
Once a sample block is selected, it does what you'd expect. If you tap middle C, you hear pretty much the original recording. It is pitch (and time) shifted for other keys up and down the scale. Chords work fine, but notes are shorter at higher freqs.
You have full synth control over the sample playback. FM, AM, filter, etc. The contour duration can limit how much of the recording is used, SUSTAIN makes the recording 'loop' which usually means a strong click at the loop point.
You might try a long recording of a single slightly-varying-but-staying-in-tune note.
Then normal length (<2 seconds) notes would have no sense of looping in their sustain, and loop-clicks would be limited to one per 2 seconds.
Maybe I will get smart and isolate some repeating clickless pattern in the middle and formally sustain by repeating that.
Anyway it's kinda fun in its own right, so it belongs in synSpace, the bag of everything!
posted
I added a soft sound effect when using long-press to store samples (or an instrument pre-set)
I worried that it might mean a sound made 'mid-performance' but then I decided the probability of that being an issue is wayyyyyyy lower than the irritant of not knowing how much longer you have to hold the button. And it was easier than a flash of light, since light has to also work around the fact that your finger is on top of the control
The vocoder display can be thought of as several rows. The topmost row shows raw filter energies (the binary search waterfall, I guess) during a recording, but then shows a different display at playback time.
The default is the spectral display, which looks a bit like a formant oscilloscope. But if you tap inside that area, you can select one of three modes of playback:
* raw samples (tap in left third) * spectrums (tap in middle third) * formant chirps (tap in right third)
Where I have the formant chirps disabled until they work better. Ultimately, I think formant chirps are what I will send between players.
raw samples sounds the closest to the original recording, of course, but understand it is playing them just a few msec at a time, then waiting for the next 'spectrum change point' and only then playing the next batch of samples, so it tends to a have a 24Hz click in it. (Worse if your device is having trouble keeping up computationally)
To which I add -- Your device is perfect, there is nothing wrong with your device. If I can't get my math done in time on your device, it is my fault, not the fault of your device.
That being said, my shiny new 10" Kindle Fire seems to be super fast, compared to all my other tablets. It also has the most lag from the time I hit a piano key to the moment I am hearing the sound. Kinda makes it unplayable in real time.
But the frame rate stays strong even while playing complex patches and rendering the filter waterfall. Not true of all my devices.
-------------------- He knows when you are sleeping. Posts: 10632 | From: California | Registered: Dec 1998
| IP: Logged